diff --git a/.gitattributes b/.gitattributes index dfe0770424..5a0d5e480b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,2 @@ # Auto detect text files and perform LF normalization -* text=auto +* text=auto eol=lf diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 6a5ed2d4a4..963fe444ec 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,11 @@ blank_issues_enabled: false contact_links: - - name: Feature Requests & Questions + - name: 🤔 Feature Requests & Questions url: https://github.com/TanStack/table/discussions about: Please ask and answer questions here. - - name: Community Chat + - name: 💬 Community Chat url: https://discord.gg/mQd7egN about: A dedicated discord server hosted by TanStack + - name: 🦋 TanStack Bluesky + url: https://bsky.app/profile/tanstack.com + about: Stay up to date with new releases of our libraries diff --git a/.github/pull_request_template b/.github/pull_request_template new file mode 100644 index 0000000000..2c10bc7d7d --- /dev/null +++ b/.github/pull_request_template @@ -0,0 +1,8 @@ +## 🎯 Changes + + + +## ✅ Checklist + +- [ ] I have followed the steps in the [Contributing guide](https://github.com/TanStack/table/blob/main/CONTRIBUTING.md). +- [ ] I have tested this code locally with `pnpm test:pr`. diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 53051c6d5f..fc5d40b75a 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -18,11 +18,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + fetch-depth: 0 + persist-credentials: false - name: Setup Tools - uses: tanstack/config/.github/setup@main + uses: tanstack/config/.github/setup@e4b48f16568324f76f467aa4c2aac2f05db632c3 # main - name: Fix formatting - run: pnpm prettier:write + run: pnpm format - name: Apply fixes uses: autofix-ci/action@dd55f44df8f7cdb7a6bf74c78677eb8acd40cd0a with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index b8ea2bf5d1..0000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: ci - -on: - workflow_dispatch: - inputs: - tag: - description: override release tag - required: false - push: - branches: [main, alpha, beta, rc] - -concurrency: - group: ${{ github.workflow }}-${{ github.event.number || github.ref }} - cancel-in-progress: true - -env: - NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} - -permissions: - contents: write - id-token: write - -jobs: - test-and-publish: - name: Test & Publish - if: github.repository == 'TanStack/table' - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Start Nx Agents - run: npx nx-cloud start-ci-run --distribute-on=".nx/workflows/dynamic-changesets.yaml" - - name: Setup Tools - uses: tanstack/config/.github/setup@main - - name: Run Tests - run: pnpm run test:ci --parallel=3 - - name: Stop Nx Agents - if: ${{ always() }} - run: npx nx-cloud stop-all-agents - - name: Publish - run: | - git config --global user.name 'Tanner Linsley' - git config --global user.email 'tannerlinsley@users.noreply.github.com' - npm config set '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}" - pnpm run cipublish - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - TAG: ${{ inputs.tag }} diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 7af298149a..ad60720c84 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -1,4 +1,4 @@ -name: pr +name: PR on: pull_request: @@ -23,15 +23,16 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 0 + persist-credentials: false - name: Start Nx Agents run: npx nx-cloud start-ci-run --distribute-on=".nx/workflows/dynamic-changesets.yaml" - name: Setup Tools - uses: tanstack/config/.github/setup@main + uses: tanstack/config/.github/setup@e4b48f16568324f76f467aa4c2aac2f05db632c3 # main - name: Get base and head commits for `nx affected` - uses: nrwl/nx-set-shas@v4 + uses: nrwl/nx-set-shas@3e9ad7370203c1e93d109be57f3b72eb0eb511b1 # v4.4.0 with: main-branch-name: main - name: Run Checks @@ -44,11 +45,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 0 + persist-credentials: false - name: Setup Tools - uses: tanstack/config/.github/setup@main + uses: tanstack/config/.github/setup@e4b48f16568324f76f467aa4c2aac2f05db632c3 # main - name: Build Packages run: pnpm run build:all - name: Publish Previews diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..b091598d83 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,50 @@ +name: Release + +on: + workflow_dispatch: + inputs: + tag: + description: override release tag + required: false + push: + branches: [main, alpha, beta, rc] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.number || github.ref }} + cancel-in-progress: true + +env: + NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} + +permissions: + contents: write + id-token: write + +jobs: + release: + name: Release + if: github.repository_owner == 'TanStack' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + fetch-depth: 0 + persist-credentials: false + - name: Start Nx Agents + run: npx nx-cloud start-ci-run --distribute-on=".nx/workflows/dynamic-changesets.yaml" + - name: Setup Tools + uses: tanstack/config/.github/setup@e4b48f16568324f76f467aa4c2aac2f05db632c3 # main + - name: Run Tests + run: pnpm run test:ci --parallel=3 + - name: Stop Nx Agents + if: ${{ always() }} + run: npx nx-cloud stop-all-agents + - name: Publish + run: | + git config --global user.name 'Tanner Linsley' + git config --global user.email 'tannerlinsley@users.noreply.github.com' + pnpm run cipublish + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ inputs.tag }} diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml new file mode 100644 index 0000000000..ba8e8cfa12 --- /dev/null +++ b/.github/workflows/zizmor.yml @@ -0,0 +1,25 @@ +name: GitHub Actions Security Analysis + +on: + push: + branches: [alpha] + pull_request: + branches: ['**'] + +permissions: {} + +jobs: + zizmor: + name: Run zizmor + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Run zizmor + uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3 + with: + advanced-security: false + annotations: true diff --git a/.gitignore b/.gitignore index 78f36fcdd4..df459c9799 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,6 @@ package-lock.json yarn.lock # builds -types build dist lib @@ -49,9 +48,18 @@ yarn.lock *.tsbuildinfo *.tsbuildinfo +.svelte-kit .nx/cache .nx/workspace-data vite.config.js.timestamp-* vite.config.ts.timestamp-* .angular + +.nx/polygraph +.claude/* +.cursor/* + +Agents.md +.agents/* +terminalOutput diff --git a/.npmrc b/.npmrc index 84aee8d998..268c392d3c 100644 --- a/.npmrc +++ b/.npmrc @@ -1,3 +1 @@ -link-workspace-packages=true -prefer-workspace-packages=true provenance=true diff --git a/.nvmrc b/.nvmrc index b8e593f521..b404027604 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -20.15.1 +24.8.0 diff --git a/.prettierignore b/.prettierignore index aa12baab9e..63dd7224e6 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,4 @@ +**/.nx/ **/.nx/cache **/.svelte-kit **/build @@ -5,6 +6,7 @@ **/dist **/docs **/old-examples +**/examples/**/*.svelte pnpm-lock.yaml .angular diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000000..1d7ac851ea --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3ec4ceec5b..287de60920 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,8 +33,9 @@ Before proceeding with development, ensure you match one of the following criter - Auto-build and auto-test files as you edit by running `pnpm dev` - Implement your changes and tests - To run examples, follow their individual directions. Usually this includes: - - Installing dependencies with `pnpm install` (from the root directory of the workspace) - - Starting the dev server with `pnpm start` (from the example directory) + - cd into the example directory + - Do NOT install dependencies again or do any linking. Nx already handles this for you. Only run install from the project root. + - Starting the dev server with `pnpm dev` or `pnpm start` (from the example directory) - To test in your own projects: - Build/watch for changes with `pnpm build`/`pnpm dev` - Document your changes in the appropriate documentation website markdown pages diff --git a/README.md b/README.md index 3b5b5284ee..4490fe2758 100644 --- a/README.md +++ b/README.md @@ -1,141 +1,117 @@ -![TanStack Table Header](https://github.com/tanstack/table/raw/main/media/repo-header.png) +
+ TanStack Table +
-# [TanStack](https://tanstack.com) Table v8 +
-Headless UI for building **powerful tables & datagrids** for **React, Solid, Vue, Svelte, Qwik and TS/JS**. - - - #TanStack - - - - +
- + npm downloads + + + github stars - + bundle size +
+ +
semantic-release - - Join the discussion on Github - - - - - - - - -> [Looking for version 7 of `react-table`? Click here!](https://github.com/tanstack/table/tree/v7) - -## Enjoy this library? - -Try other [TanStack](https://tanstack.com) libraries: - -- [TanStack Query](https://github.com/TanStack/query) -- [TanStack Table](https://github.com/TanStack/table) -- [TanStack Router](https://github.com/TanStack/router) -- [TanStack Virtual](https://github.com/TanStack/virtual) -- [TanStack Form](https://github.com/TanStack/form) -- [TanStack Ranger](https://github.com/TanStack/ranger) - -## Visit [tanstack.com/table](https://tanstack.com/table) for docs, guides, API and more! - -You may know **TanStack Table** by our adapter names, too! - -- [Angular Table](https://tanstack.com/table/v8/docs/framework/angular/angular-table) -- [Lit Table](https://tanstack.com/table/v8/docs/framework/lit/lit-table) -- [Qwik Table](https://tanstack.com/table/v8/docs/framework/qwik/qwik-table) -- [**React Table**](https://tanstack.com/table/v8/docs/framework/react/react-table) -- [Solid Table](https://tanstack.com/table/v8/docs/framework/solid/solid-table) -- [Svelte Table](https://tanstack.com/table/v8/docs/framework/svelte/svelte-table) -- [Vue Table](https://tanstack.com/table/v8/docs/framework/vue/vue-table) - -## Summary - -TanStack Table is a **headless** table library, which means it does not ship with components, markup or styles. This means that you have **full control** over markup and styles (CSS, CSS-in-JS, UI Component Libraries, etc) and this is also what gives it its portable nature. You can even use it in React Native! - -If you want a **lightweight table with full control over markup and implementation**, then you should consider using **TanStack Table, a headless table library**. - -If you want a **ready-to-use component-based table with more power but more constraints around markup/styles/implementation**, you should consider using [AG Grid](https://ag-grid.com/react-data-grid/?utm_source=reacttable&utm_campaign=githubreacttable), a component-based table library from our OSS partner [AG Grid](https://ag-grid.com). - -TanStack Table and AG Grid are respectfully the -**best table/datagrid libraries around**. Instead -of competing, we're working together to ensure the highest -quality table/datagrid options are available for the entire -JS/TS ecosystem and every use-case. - -## Quick Features - -- Agnostic core (JS/TS) -- 1st-class framework bindings for React, Vue, Solid -- ~15kb or less (with tree-shaking) -- 100% TypeScript (but not required) -- Headless (100% customizable, Bring-your-own-UI) -- Auto out of the box, opt-in controllable state -- Filters (column and global) -- Sorting (multi-column, multi-directional) -- Grouping & Aggregation -- Pivoting (coming soon!) -- Row Selection -- Row Expansion -- Column Visibility/Ordering/Pinning/Resizing -- Table Splitting -- Animatable -- Virtualizable -- Server-side/external data model support - -# Migrating from React Table v7 - -## Notable Changes - -- Full rewrite to TypeScript with types included in the base package -- Removal of plugin system to favor more inversion of control -- Vastly larger and improved API (and new features like pinning) -- Better controlled state management -- Better support for server-side operations -- Complete (but optional) data pipeline control -- Agnostic core with framework adapters for React, Solid, Svelte, Vue, and potentially more in the future -- New Dev Tools - -## Migration - -There are a fair amount of breaking changes (they're worth it, trust us!): - -- Turns out that TypeScript makes your code **a lot** better/safer, but also usually requires breaking changes to architecture. -- Plugin system has been removed so plugins must be rewritten to wrap/compose the new functional API. Contact us if you need help! -- Column configuration options have changed, but only slightly. -- Table options are mostly the same, with some larger changes around optional state management/control and data pipeline control -- The `table` instance while similar in spirit to v7 has been reconfigured to be much faster. - -## Installation - -Install one of the following packages based on your framework of choice: - -```bash -# Npm -npm install @tanstack/angular-table -npm install @tanstack/lit-table -npm install @tanstack/qwik-table -npm install @tanstack/react-table -npm install @tanstack/solid-table -npm install @tanstack/svelte-table -npm install @tanstack/vue-table -npm install @tanstack/table-core #vanilla js that can work with any framework -``` - -## How to help? - -- Try out the already-migrated examples -- Try it out in your own projects. -- Introspect the types! Even without the docs finished, the library ships with 100% typescript to help you explore its capabilities. -- [Read the contribution guidelines](https://github.com/tanstack/table/tree/main/CONTRIBUTING.md) -- Write some docs! Start with the [API docs](https://github.com/TanStack/react-table/tree/main/docs/api) and try adding some information about one or more of the features. The types do a decent job of showing what's supported and the capabilities of the library. -- **Using a plugin?** Try rewriting your plugin (v8 doesn't have a plugin system any more) as a functional wrapper that uses TanStack Table internally. The new API is much more powerful and easier to compose. If you find something you can't figure out, let us know and we'll add it to the API. - -### [Become a Sponsor](https://github.com/sponsors/tannerlinsley/) +Best of JS + Follow @TanStack +
+ +### [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/) + + + +# TanStack Table + +> [!NOTE] +> You may know TanStack Table by the adapter names: +> +> - [Angular Table](https://tanstack.com/table/alpha/docs/framework/angular/angular-table) +> - [Lit Table](https://tanstack.com/table/alpha/docs/framework/lit/lit-table) +> - [React Table](https://tanstack.com/table/alpha/docs/framework/react/react-table) +> - [Solid Table](https://tanstack.com/table/alpha/docs/framework/solid/solid-table) +> - [Svelte Table](https://tanstack.com/table/alpha/docs/framework/svelte/svelte-table) +> - [Vue Table](https://tanstack.com/table/alpha/docs/framework/vue/vue-table) + +A headless table library for building powerful datagrids with full control over markup, styles, and behavior. + +- Framework‑agnostic core with bindings for React, Vue & Solid +- 100% customizable — bring your own UI, components, and styles +- Sorting, filtering, grouping, aggregation & row selection +- Lightweight, virtualizable & server‑side friendly + +### Read the Docs → + +## Get Involved + +- We welcome issues and pull requests! +- Participate in [GitHub discussions](https://github.com/TanStack/table/discussions) +- Chat with the community on [Discord](https://discord.com/invite/WrRKjPJ) +- See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions + +## Partners + + + + + + + +
+ + + + + CodeRabbit + + + + + + + + Cloudflare + + + + + + + + AG Grid + + +
+ +
+Table & you? +

+We're looking for TanStack Table Partners to join our mission! Partner with us to push the boundaries of TanStack Table and build amazing things together. +

+LET'S CHAT +
+ +## Explore the TanStack Ecosystem + +- TanStack Config – Tooling for JS/TS packages +- TanStack DB – Reactive sync client store +- TanStack DevTools – Unified devtools panel +- TanStack Form – Type‑safe form state +- TanStack Pacer – Debouncing, throttling, batching
+- TanStack Query – Async state & caching +- TanStack Ranger – Range & slider primitives +- TanStack Router – Type‑safe routing, caching & URL state +- TanStack Start – Full‑stack SSR & streaming +- TanStack Store – Reactive data store +- TanStack Virtual – Virtualized rendering + +… and more at TanStack.com » diff --git a/babel.config.cjs b/babel.config.cjs deleted file mode 100644 index 84a3ee4ebe..0000000000 --- a/babel.config.cjs +++ /dev/null @@ -1,35 +0,0 @@ -const { NODE_ENV, BABEL_ENV } = process.env -const cjs = NODE_ENV === 'test' || BABEL_ENV === 'commonjs' -const loose = true - -module.exports = { - targets: 'defaults, not ie 11, not ie_mob 11', - presets: [ - [ - '@babel/preset-env', - { - loose, - modules: false, - include: [ - '@babel/plugin-proposal-nullish-coalescing-operator', - '@babel/plugin-proposal-optional-chaining', - ], - // exclude: ['@babel/plugin-transform-regenerator'], - }, - ], - '@babel/react', - '@babel/preset-typescript', - ], - plugins: [ - cjs && ['@babel/transform-modules-commonjs', { loose }], - // [ - // '@babel/transform-runtime', - // { - // useESModules: !cjs, - // version: require('./package.json').dependencies[ - // '@babel/runtime' - // ].replace(/^[^0-9]*/, ''), - // }, - // ], - ].filter(Boolean), -} diff --git a/docs/api/core/cell.md b/docs/api/core/cell.md deleted file mode 100644 index 6c1ff4a073..0000000000 --- a/docs/api/core/cell.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -title: Cell APIs ---- - -These are **core** options and API properties for all cells. More options and API properties are available for other [table features](../../../guide/features). - -## Cell API - -All cell objects have the following properties: - -### `id` - -```tsx -id: string -``` - -The unique ID for the cell across the entire table. - -### `getValue` - -```tsx -getValue: () => any -``` - -Returns the value for the cell, accessed via the associated column's accessor key or accessor function. - -### `renderValue` - -```tsx -renderValue: () => any -``` - -Renders the value for a cell the same as `getValue`, but will return the `renderFallbackValue` if no value is found. - -### `row` - -```tsx -row: Row -``` - -The associated Row object for the cell. - -### `column` - -```tsx -column: Column -``` - -The associated Column object for the cell. - -### `getContext` - -```tsx -getContext: () => { - table: Table - column: Column - row: Row - cell: Cell - getValue: () => TTValue - renderValue: () => TTValue | null -} -``` - -Returns the rendering context (or props) for cell-based components like cells and aggregated cells. Use these props with your framework's `flexRender` utility to render these using the template of your choice: - -```tsx -flexRender(cell.column.columnDef.cell, cell.getContext()) -``` diff --git a/docs/api/core/column-def.md b/docs/api/core/column-def.md deleted file mode 100644 index 3522e9da62..0000000000 --- a/docs/api/core/column-def.md +++ /dev/null @@ -1,107 +0,0 @@ ---- -title: ColumnDef APIs ---- - -Column definitions are plain objects with the following options: - -## Options - -### `id` - -```tsx -id: string -``` - -The unique identifier for the column. - -> 🧠 A column ID is optional when: -> -> - An accessor column is created with an object key accessor -> - The column header is defined as a string - -### `accessorKey` - -```tsx -accessorKey?: string & typeof TData -``` - -The key of the row object to use when extracting the value for the column. - -### `accessorFn` - -```tsx -accessorFn?: (originalRow: TData, index: number) => any -``` - -The accessor function to use when extracting the value for the column from each row. - -### `columns` - -```tsx -columns?: ColumnDef[] -``` - -The child column defs to include in a group column. - -### `header` - -```tsx -header?: - | string - | ((props: { - table: Table - header: Header - column: Column - }) => unknown) -``` - -The header to display for the column. If a string is passed, it can be used as a default for the column ID. If a function is passed, it will be passed a props object for the header and should return the rendered header value (the exact type depends on the adapter being used). - -### `footer` - -```tsx -footer?: - | string - | ((props: { - table: Table - header: Header - column: Column - }) => unknown) -``` - -The footer to display for the column. If a function is passed, it will be passed a props object for the footer and should return the rendered footer value (the exact type depends on the adapter being used). - -### `cell` - -```tsx -cell?: - | string - | ((props: { - table: Table - row: Row - column: Column - cell: Cell - getValue: () => any - renderValue: () => any - }) => unknown) -``` - -The cell to display each row for the column. If a function is passed, it will be passed a props object for the cell and should return the rendered cell value (the exact type depends on the adapter being used). - -### `meta` - -```tsx -meta?: ColumnMeta // This interface is extensible via declaration merging. See below! -``` - -The meta data to be associated with the column. We can access it anywhere when the column is available via `column.columnDef.meta`. This type is global to all tables and can be extended like so: - -```tsx -import '@tanstack/react-table' //or vue, svelte, solid, qwik, etc. - -declare module '@tanstack/react-table' { - interface ColumnMeta { - foo: string - } -} -``` diff --git a/docs/api/core/column.md b/docs/api/core/column.md deleted file mode 100644 index 626287b836..0000000000 --- a/docs/api/core/column.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -title: Column APIs ---- - -These are **core** options and API properties for all columns. More options and API properties are available for other [table features](../../../guide/features). - -## Column API - -All column objects have the following properties: - -### `id` - -```tsx -id: string -``` - -The resolved unique identifier for the column resolved in this priority: - -- A manual `id` property from the column def -- The accessor key from the column def -- The header string from the column def - -### `depth` - -```tsx -depth: number -``` - -The depth of the column (if grouped) relative to the root column def array. - -### `accessorFn` - -```tsx -accessorFn?: AccessorFn -``` - -The resolved accessor function to use when extracting the value for the column from each row. Will only be defined if the column def has a valid accessor key or function defined. - -### `columnDef` - -```tsx -columnDef: ColumnDef -``` - -The original column def used to create the column. - -### `columns` - -```tsx -type columns = ColumnDef[] -``` - -The child column (if the column is a group column). Will be an empty array if the column is not a group column. - -### `parent` - -```tsx -parent?: Column -``` - -The parent column for this column. Will be undefined if this is a root column. - -### `getFlatColumns` - -```tsx -type getFlatColumns = () => Column[] -``` - -Returns the flattened array of this column and all child/grand-child columns for this column. - -### `getLeafColumns` - -```tsx -type getLeafColumns = () => Column[] -``` - -Returns an array of all leaf-node columns for this column. If a column has no children, it is considered the only leaf-node column. diff --git a/docs/api/core/header-group.md b/docs/api/core/header-group.md deleted file mode 100644 index 022e7bdff2..0000000000 --- a/docs/api/core/header-group.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: HeaderGroup APIs ---- - -These are **core** options and API properties for all header groups. More options and API properties may be available for other [table features](../../../guide/features). - -## Header Group API - -All header group objects have the following properties: - -### `id` - -```tsx -id: string -``` - -The unique identifier for the header group. - -### `depth` - -```tsx -depth: number -``` - -The depth of the header group, zero-indexed based. - -### `headers` - -```tsx -type headers = Header[] -``` - -An array of [Header](../header) objects that belong to this header group diff --git a/docs/api/core/header.md b/docs/api/core/header.md deleted file mode 100644 index 120a9fc29a..0000000000 --- a/docs/api/core/header.md +++ /dev/null @@ -1,243 +0,0 @@ ---- -title: Header APIs ---- - -These are **core** options and API properties for all headers. More options and API properties may be available for other [table features](../../../guide/features). - -## Header API - -All header objects have the following properties: - -### `id` - -```tsx -id: string -``` - -The unique identifier for the header. - -### `index` - -```tsx -index: number -``` - -The index for the header within the header group. - -### `depth` - -```tsx -depth: number -``` - -The depth of the header, zero-indexed based. - -### `column` - -```tsx -column: Column -``` - -The header's associated [Column](../column) object - -### `headerGroup` - -```tsx -headerGroup: HeaderGroup -``` - -The header's associated [HeaderGroup](../header-group) object - -### `subHeaders` - -```tsx -type subHeaders = Header[] -``` - -The header's hierarchical sub/child headers. Will be empty if the header's associated column is a leaf-column. - -### `colSpan` - -```tsx -colSpan: number -``` - -The col-span for the header. - -### `rowSpan` - -```tsx -rowSpan: number -``` - -The row-span for the header. - -### `getLeafHeaders` - -```tsx -type getLeafHeaders = () => Header[] -``` - -Returns the leaf headers hierarchically nested under this header. - -### `isPlaceholder` - -```tsx -isPlaceholder: boolean -``` - -A boolean denoting if the header is a placeholder header - -### `placeholderId` - -```tsx -placeholderId?: string -``` - -If the header is a placeholder header, this will be a unique header ID that does not conflict with any other headers across the table - -### `getContext` - -```tsx -getContext: () => { - table: Table - header: Header - column: Column -} -``` - -Returns the rendering context (or props) for column-based components like headers, footers and filters. Use these props with your framework's `flexRender` utility to render these using the template of your choice: - -```tsx -flexRender(header.column.columnDef.header, header.getContext()) -``` - -## Table API - -### `getHeaderGroups` - -```tsx -type getHeaderGroups = () => HeaderGroup[] -``` - -Returns all header groups for the table. - -### `getLeftHeaderGroups` - -```tsx -type getLeftHeaderGroups = () => HeaderGroup[] -``` - -If pinning, returns the header groups for the left pinned columns. - -### `getCenterHeaderGroups` - -```tsx -type getCenterHeaderGroups = () => HeaderGroup[] -``` - -If pinning, returns the header groups for columns that are not pinned. - -### `getRightHeaderGroups` - -```tsx -type getRightHeaderGroups = () => HeaderGroup[] -``` - -If pinning, returns the header groups for the right pinned columns. - -### `getFooterGroups` - -```tsx -type getFooterGroups = () => HeaderGroup[] -``` - -Returns all footer groups for the table. - -### `getLeftFooterGroups` - -```tsx -type getLeftFooterGroups = () => HeaderGroup[] -``` - -If pinning, returns the footer groups for the left pinned columns. - -### `getCenterFooterGroups` - -```tsx -type getCenterFooterGroups = () => HeaderGroup[] -``` - -If pinning, returns the footer groups for columns that are not pinned. - -### `getRightFooterGroups` - -```tsx -type getRightFooterGroups = () => HeaderGroup[] -``` - -If pinning, returns the footer groups for the right pinned columns. - -### `getFlatHeaders` - -```tsx -type getFlatHeaders = () => Header[] -``` - -Returns headers for all columns in the table, including parent headers. - -### `getLeftFlatHeaders` - -```tsx -type getLeftFlatHeaders = () => Header[] -``` - -If pinning, returns headers for all left pinned columns in the table, including parent headers. - -### `getCenterFlatHeaders` - -```tsx -type getCenterFlatHeaders = () => Header[] -``` - -If pinning, returns headers for all columns that are not pinned, including parent headers. - -### `getRightFlatHeaders` - -```tsx -type getRightFlatHeaders = () => Header[] -``` - -If pinning, returns headers for all right pinned columns in the table, including parent headers. - -### `getLeafHeaders` - -```tsx -type getLeafHeaders = () => Header[] -``` - -Returns headers for all leaf columns in the table, (not including parent headers). - -### `getLeftLeafHeaders` - -```tsx -type getLeftLeafHeaders = () => Header[] -``` - -If pinning, returns headers for all left pinned leaf columns in the table, (not including parent headers). - -### `getCenterLeafHeaders` - -```tsx -type getCenterLeafHeaders = () => Header[] -``` - -If pinning, returns headers for all columns that are not pinned, (not including parent headers). - -### `getRightLeafHeaders` - -```tsx -type getRightLeafHeaders = () => Header[] -``` - -If pinning, returns headers for all right pinned leaf columns in the table, (not including parent headers). diff --git a/docs/api/core/row.md b/docs/api/core/row.md deleted file mode 100644 index b010df2811..0000000000 --- a/docs/api/core/row.md +++ /dev/null @@ -1,123 +0,0 @@ ---- -title: Row APIs ---- - -These are **core** options and API properties for all rows. More options and API properties are available for other [table features](../../../guide/features). - -## Row API - -All row objects have the following properties: - -### `id` - -```tsx -id: string -``` - -The resolved unique identifier for the row resolved via the `options.getRowId` option. Defaults to the row's index (or relative index if it is a subRow) - -### `depth` - -```tsx -depth: number -``` - -The depth of the row (if nested or grouped) relative to the root row array. - -### `index` - -```tsx -index: number -``` - -The index of the row within its parent array (or the root data array) - -### `original` - -```tsx -original: TData -``` - -The original row object provided to the table. - -> 🧠 If the row is a grouped row, the original row object will be the first original in the group. - -### `parentId` - -```tsx -parentId?: string -``` - -If nested, this row's parent row id. - -### `getValue` - -```tsx -getValue: (columnId: string) => TValue -``` - -Returns the value from the row for a given columnId - -### `renderValue` - -```tsx -renderValue: (columnId: string) => TValue -``` - -Renders the value from the row for a given columnId, but will return the `renderFallbackValue` if no value is found. - -### `getUniqueValues` - -```tsx -getUniqueValues: (columnId: string) => TValue[] -``` - -Returns a unique array of values from the row for a given columnId. - -### `subRows` - -```tsx -type subRows = Row[] -``` - -An array of subRows for the row as returned and created by the `options.getSubRows` option. - -### `getParentRow` - -```tsx -type getParentRow = () => Row | undefined -``` - -Returns the parent row for the row, if it exists. - -### `getParentRows` - -```tsx -type getParentRows = () => Row[] -``` - -Returns the parent rows for the row, all the way up to a root row. - -### `getLeafRows` - -```tsx -type getLeafRows = () => Row[] -``` - -Returns the leaf rows for the row, not including any parent rows. - -### `originalSubRows` - -```tsx -originalSubRows?: TData[] -``` - -An array of the original subRows as returned by the `options.getSubRows` option. - -### `getAllCells` - -```tsx -type getAllCells = () => Cell[] -``` - -Returns all of the [Cells](../cell) for the row. diff --git a/docs/api/core/table.md b/docs/api/core/table.md deleted file mode 100644 index bd2415bf57..0000000000 --- a/docs/api/core/table.md +++ /dev/null @@ -1,385 +0,0 @@ ---- -title: Table APIs ---- - -## `createAngularTable` / `useReactTable` / `createSolidTable` / `useQwikTable` / `useVueTable` / `createSvelteTable` - -```tsx -type useReactTable = ( - options: TableOptions -) => Table -``` - -These functions are used to create a table. Which one you use depends on which framework adapter you are using. - -## Options - -These are **core** options and API properties for the table. More options and API properties are available for other [table features](../../../guide/features). - -### `data` - -```tsx -data: TData[] -``` - -The data for the table to display. This array should match the type you provided to `table.setRowType<...>`, but in theory could be an array of anything. It's common for each item in the array to be an object of key/values but this is not required. Columns can access this data via string/index or a functional accessor to return anything they want. - -When the `data` option changes reference (compared via `Object.is`), the table will reprocess the data. Any other data processing that relies on the core data model (such as grouping, sorting, filtering, etc) will also be reprocessed. - -> 🧠 Make sure your `data` option is only changing when you want the table to reprocess. Providing an inline `[]` or constructing the data array as a new object every time you want to render the table will result in a _lot_ of unnecessary re-processing. This can easily go unnoticed in smaller tables, but you will likely notice it in larger tables. - -### `columns` - -```tsx -type columns = ColumnDef[] -``` - -The array of column defs to use for the table. See the [Column Defs Guide](../../docs/guide/column-defs) for more information on creating column definitions. - -### `defaultColumn` - -```tsx -defaultColumn?: Partial> -``` - -Default column options to use for all column defs supplied to the table. This is useful for providing default cell/header/footer renderers, sorting/filtering/grouping options, etc. All column definitions passed to `options.columns` are merged with this default column definition to produce the final column definitions. - -### `initialState` - -```tsx -initialState?: Partial< - VisibilityTableState & - ColumnOrderTableState & - ColumnPinningTableState & - FiltersTableState & - SortingTableState & - ExpandedTableState & - GroupingTableState & - ColumnSizingTableState & - PaginationTableState & - RowSelectionTableState -> -``` - -Use this option to optionally pass initial state to the table. This state will be used when resetting various table states either automatically by the table (eg. `options.autoResetPageIndex`) or via functions like `table.resetRowSelection()`. Most reset function allow you optionally pass a flag to reset to a blank/default state instead of the initial state. - -> 🧠 Table state will not be reset when this object changes, which also means that the initial state object does not need to be stable. - -### `autoResetAll` - -```tsx -autoResetAll?: boolean -``` - -Set this option to override any of the `autoReset...` feature options. - -### `meta` - -```tsx -meta?: TableMeta // This interface is extensible via declaration merging. See below! -``` - -You can pass any object to `options.meta` and access it anywhere the `table` is available via `table.options.meta` This type is global to all tables and can be extended like so: - -```tsx -declare module '@tanstack/table-core' { - interface TableMeta { - foo: string - } -} -``` - -> 🧠 Think of this option as an arbitrary "context" for your table. This is a great way to pass arbitrary data or functions to your table without having to pass it to every thing the table touches. A good example is passing a locale object to your table to use for formatting dates, numbers, etc or even a function that can be used to update editable data like in the [editable-data example](../../../framework/react/examples/editable-data). - -### `state` - -```tsx -state?: Partial< - VisibilityTableState & - ColumnOrderTableState & - ColumnPinningTableState & - FiltersTableState & - SortingTableState & - ExpandedTableState & - GroupingTableState & - ColumnSizingTableState & - PaginationTableState & - RowSelectionTableState -> -``` - -The `state` option can be used to optionally _control_ part or all of the table state. The state you pass here will merge with and overwrite the internal automatically-managed state to produce the final state for the table. You can also listen to state changes via the `onStateChange` option. - -### `onStateChange` - -```tsx -onStateChange: (updater: Updater) => void -``` - -The `onStateChange` option can be used to optionally listen to state changes within the table. If you provide this options, you will be responsible for controlling and updating the table state yourself. You can provide the state back to the table with the `state` option. - -### `debugAll` - -> ⚠️ Debugging is only available in development mode. - -```tsx -debugAll?: boolean -``` - -Set this option to true to output all debugging information to the console. - -### `debugTable` - -> ⚠️ Debugging is only available in development mode. - -```tsx -debugTable?: boolean -``` - -Set this option to true to output table debugging information to the console. - -### `debugHeaders` - -> ⚠️ Debugging is only available in development mode. - -```tsx -debugHeaders?: boolean -``` - -Set this option to true to output header debugging information to the console. - -### `debugColumns` - -> ⚠️ Debugging is only available in development mode. - -```tsx -debugColumns?: boolean -``` - -Set this option to true to output column debugging information to the console. - -### `debugRows` - -> ⚠️ Debugging is only available in development mode. - -```tsx -debugRows?: boolean -``` - -Set this option to true to output row debugging information to the console. - -### `_features` - -```tsx -_features?: TableFeature[] -``` - -An array of extra features that you can add to the table instance. - -### `render` - -> ⚠️ This option is only necessary if you are implementing a table adapter. - -```tsx -type render = (template: Renderable, props: TProps) => any -``` - -The `render` option provides a renderer implementation for the table. This implementation is used to turn a table's various column header and cell templates into a result that is supported by the user's framework. - -### `mergeOptions` - -> ⚠️ This option is only necessary if you are implementing a table adapter. - -```tsx -type mergeOptions = (defaultOptions: T, options: Partial) => T -``` - -This option is used to optionally implement the merging of table options. Some framework like solid-js use proxies to track reactivity and usage, so merging reactive objects needs to be handled carefully. This option inverts control of this process to the adapter. - -### `getCoreRowModel` - -```tsx -getCoreRowModel: (table: Table) => () => RowModel -``` - -This required option is a factory for a function that computes and returns the core row model for the table. It is called **once** per table and should return a **new function** which will calculate and return the row model for the table. - -A default implementation is provided via any table adapter's `{ getCoreRowModel }` export. - -### `getSubRows` - -```tsx -getSubRows?: ( - originalRow: TData, - index: number -) => undefined | TData[] -``` - -This optional function is used to access the sub rows for any given row. If you are using nested rows, you will need to use this function to return the sub rows object (or undefined) from the row. - -### `getRowId` - -```tsx -getRowId?: ( - originalRow: TData, - index: number, - parent?: Row -) => string -``` - -This optional function is used to derive a unique ID for any given row. If not provided the rows index is used (nested rows join together with `.` using their grandparents' index eg. `index.index.index`). If you need to identify individual rows that are originating from any server-side operations, it's suggested you use this function to return an ID that makes sense regardless of network IO/ambiguity eg. a userId, taskId, database ID field, etc. - -## Table API - -These properties and methods are available on the table object: - -### `initialState` - -```tsx -initialState: VisibilityTableState & - ColumnOrderTableState & - ColumnPinningTableState & - FiltersTableState & - SortingTableState & - ExpandedTableState & - GroupingTableState & - ColumnSizingTableState & - PaginationTableState & - RowSelectionTableState -``` - -This is the resolved initial state of the table. - -### `reset` - -```tsx -reset: () => void -``` - -Call this function to reset the table state to the initial state. - -### `getState` - -```tsx -getState: () => TableState -``` - -Call this function to get the table's current state. It's recommended to use this function and its state, especially when managing the table state manually. It is the exact same state used internally by the table for every feature and function it provides. - -> 🧠 The state returned by this function is the shallow-merged result of the automatically-managed internal table-state and any manually-managed state passed via `options.state`. - -### `setState` - -```tsx -setState: (updater: Updater) => void -``` - -Call this function to update the table state. It's recommended you pass an updater function in the form of `(prevState) => newState` to update the state, but a direct object can also be passed. - -> 🧠 If `options.onStateChange` is provided, it will be triggered by this function with the new state. - -### `options` - -```tsx -options: TableOptions -``` - -A read-only reference to the table's current options. - -> ⚠️ This property is generally used internally or by adapters. It can be updated by passing new options to your table. This is different per adapter. For adapters themselves, table options must be updated via the `setOptions` function. - -### `setOptions` - -```tsx -setOptions: (newOptions: Updater>) => void -``` - -> ⚠️ This function is generally used by adapters to update the table options. It can be used to update the table options directly, but it is generally not recommended to bypass your adapters strategy for updating table options. - -### `getCoreRowModel` - -```tsx -getCoreRowModel: () => { - rows: Row[], - flatRows: Row[], - rowsById: Record>, -} -``` - -Returns the core row model before any processing has been applied. - -### `getRowModel` - -```tsx -getRowModel: () => { - rows: Row[], - flatRows: Row[], - rowsById: Record>, -} -``` - -Returns the final model after all processing from other used features has been applied. - -### `getAllColumns` - -```tsx -type getAllColumns = () => Column[] -``` - -Returns all columns in the table in their normalized and nested hierarchy, mirrored from the column defs passed to the table. - -### `getAllFlatColumns` - -```tsx -type getAllFlatColumns = () => Column[] -``` - -Returns all columns in the table flattened to a single level. This includes parent column objects throughout the hierarchy. - -### `getAllLeafColumns` - -```tsx -type getAllLeafColumns = () => Column[] -``` - -Returns all leaf-node columns in the table flattened to a single level. This does not include parent columns. - -### `getColumn` - -```tsx -type getColumn = (id: string) => Column | undefined -``` - -Returns a single column by its ID. - -### `getHeaderGroups` - -```tsx -type getHeaderGroups = () => HeaderGroup[] -``` - -Returns the header groups for the table. - -### `getFooterGroups` - -```tsx -type getFooterGroups = () => HeaderGroup[] -``` - -Returns the footer groups for the table. - -### `getFlatHeaders` - -```tsx -type getFlatHeaders = () => Header[] -``` - -Returns a flattened array of Header objects for the table, including parent headers. - -### `getLeafHeaders` - -```tsx -type getLeafHeaders = () => Header[] -``` - -Returns a flattened array of leaf-node Header objects for the table. diff --git a/docs/api/features/column-faceting.md b/docs/api/features/column-faceting.md deleted file mode 100644 index 2a951da447..0000000000 --- a/docs/api/features/column-faceting.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: Column Faceting APIs -id: column-faceting ---- - -## Column API - -### `getFacetedRowModel` - -```tsx -type getFacetedRowModel = () => RowModel -``` - -> ⚠️ Requires that you pass a valid `getFacetedRowModel` function to `options.facetedRowModel`. A default implementation is provided via the exported `getFacetedRowModel` function. - -Returns the row model with all other column filters applied, excluding its own filter. Useful for displaying faceted result counts. - -### `getFacetedUniqueValues` - -```tsx -getFacetedUniqueValues: () => Map -``` - -> ⚠️ Requires that you pass a valid `getFacetedUniqueValues` function to `options.getFacetedUniqueValues`. A default implementation is provided via the exported `getFacetedUniqueValues` function. - -A function that **computes and returns** a `Map` of unique values and their occurrences derived from `column.getFacetedRowModel`. Useful for displaying faceted result values. - -### `getFacetedMinMaxValues` - -```tsx -getFacetedMinMaxValues: () => Map -``` - -> ⚠️ Requires that you pass a valid `getFacetedMinMaxValues` function to `options.getFacetedMinMaxValues`. A default implementation is provided via the exported `getFacetedMinMaxValues` function. - -A function that **computes and returns** a min/max tuple derived from `column.getFacetedRowModel`. Useful for displaying faceted result values. - -## Table Options - -### `getColumnFacetedRowModel` - -```tsx -getColumnFacetedRowModel: (columnId: string) => RowModel -``` - -Returns the faceted row model for a given columnId. diff --git a/docs/api/features/column-filtering.md b/docs/api/features/column-filtering.md deleted file mode 100644 index b32d4f314c..0000000000 --- a/docs/api/features/column-filtering.md +++ /dev/null @@ -1,396 +0,0 @@ ---- -title: Column Filtering APIs -id: column-filtering ---- - -## Can-Filter - -The ability for a column to be **column** filtered is determined by the following: - -- The column was defined with a valid `accessorKey`/`accessorFn`. -- `column.enableColumnFilter` is not set to `false` -- `options.enableColumnFilters` is not set to `false` -- `options.enableFilters` is not set to `false` - -## State - -Filter state is stored on the table using the following shape: - -```tsx -export interface ColumnFiltersTableState { - columnFilters: ColumnFiltersState -} - -export type ColumnFiltersState = ColumnFilter[] - -export interface ColumnFilter { - id: string - value: unknown -} -``` - -## Filter Functions - -The following filter functions are built-in to the table core: - -- `includesString` - - Case-insensitive string inclusion -- `includesStringSensitive` - - Case-sensitive string inclusion -- `equalsString` - - Case-insensitive string equality -- `equalsStringSensitive` - - Case-sensitive string equality -- `arrIncludes` - - Item inclusion within an array -- `arrIncludesAll` - - All items included in an array -- `arrIncludesSome` - - Some items included in an array -- `equals` - - Object/referential equality `Object.is`/`===` -- `weakEquals` - - Weak object/referential equality `==` -- `inNumberRange` - - Number range inclusion - -Every filter function receives: - -- The row to filter -- The columnId to use to retrieve the row's value -- The filter value - -and should return `true` if the row should be included in the filtered rows, and `false` if it should be removed. - -This is the type signature for every filter function: - -```tsx -export type FilterFn = { - ( - row: Row, - columnId: string, - filterValue: any, - addMeta: (meta: any) => void - ): boolean - resolveFilterValue?: TransformFilterValueFn - autoRemove?: ColumnFilterAutoRemoveTestFn - addMeta?: (meta?: any) => void -} - -export type TransformFilterValueFn = ( - value: any, - column?: Column -) => unknown - -export type ColumnFilterAutoRemoveTestFn = ( - value: any, - column?: Column -) => boolean - -export type CustomFilterFns = Record< - string, - FilterFn -> -``` - -### `filterFn.resolveFilterValue` - -This optional "hanging" method on any given `filterFn` allows the filter function to transform/sanitize/format the filter value before it is passed to the filter function. - -### `filterFn.autoRemove` - -This optional "hanging" method on any given `filterFn` is passed a filter value and expected to return `true` if the filter value should be removed from the filter state. eg. Some boolean-style filters may want to remove the filter value from the table state if the filter value is set to `false`. - -#### Using Filter Functions - -Filter functions can be used/referenced/defined by passing the following to `columnDefinition.filterFn`: - -- A `string` that references a built-in filter function -- A function directly provided to the `columnDefinition.filterFn` option - -The final list of filter functions available for the `columnDef.filterFn` option use the following type: - -```tsx -export type FilterFnOption = - | 'auto' - | BuiltInFilterFn - | FilterFn -``` - -#### Filter Meta - -Filtering data can often expose additional information about the data that can be used to aid other future operations on the same data. A good example of this concept is a ranking-system like that of [`match-sorter`](https://github.com/kentcdodds/match-sorter) that simultaneously ranks, filters and sorts data. While utilities like `match-sorter` make a lot of sense for single-dimensional filter+sort tasks, the decoupled filtering/sorting architecture of building a table makes them very difficult and slow to use. - -To make a ranking/filtering/sorting system work with tables, `filterFn`s can optionally mark results with a **filter meta** value that can be used later to sort/group/etc the data to your liking. This is done by calling the `addMeta` function supplied to your custom `filterFn`. - -Below is an example using our own `match-sorter-utils` package (a utility fork of `match-sorter`) to rank, filter, and sort the data - -```tsx -import { sortingFns } from '@tanstack/react-table' - -import { rankItem, compareItems } from '@tanstack/match-sorter-utils' - -const fuzzyFilter = (row, columnId, value, addMeta) => { - // Rank the item - const itemRank = rankItem(row.getValue(columnId), value) - - // Store the ranking info - addMeta(itemRank) - - // Return if the item should be filtered in/out - return itemRank.passed -} - -const fuzzySort = (rowA, rowB, columnId) => { - let dir = 0 - - // Only sort by rank if the column has ranking information - if (rowA.columnFiltersMeta[columnId]) { - dir = compareItems( - rowA.columnFiltersMeta[columnId]!, - rowB.columnFiltersMeta[columnId]! - ) - } - - // Provide an alphanumeric fallback for when the item ranks are equal - return dir === 0 ? sortingFns.alphanumeric(rowA, rowB, columnId) : dir -} -``` - -## Column Def Options - -### `filterFn` - -```tsx -filterFn?: FilterFn | keyof FilterFns | keyof BuiltInFilterFns -``` - -The filter function to use with this column. - -Options: - -- A `string` referencing a [built-in filter function](#filter-functions)) -- A [custom filter function](#filter-functions) - -### `enableColumnFilter` - -```tsx -enableColumnFilter?: boolean -``` - -Enables/disables the **column** filter for this column. - -## Column API - -### `getCanFilter` - -```tsx -getCanFilter: () => boolean -``` - -Returns whether or not the column can be **column** filtered. - -### `getFilterIndex` - -```tsx -getFilterIndex: () => number -``` - -Returns the index (including `-1`) of the column filter in the table's `state.columnFilters` array. - -### `getIsFiltered` - -```tsx -getIsFiltered: () => boolean -``` - -Returns whether or not the column is currently filtered. - -### `getFilterValue` - -```tsx -getFilterValue: () => unknown -``` - -Returns the current filter value of the column. - -### `setFilterValue` - -```tsx -setFilterValue: (updater: Updater) => void -``` - -A function that sets the current filter value for the column. You can pass it a value or an updater function for immutability-safe operations on existing values. - -### `getAutoFilterFn` - -```tsx -getAutoFilterFn: (columnId: string) => FilterFn | undefined -``` - -Returns an automatically calculated filter function for the column based off of the columns first known value. - -### `getFilterFn` - -```tsx -getFilterFn: (columnId: string) => FilterFn | undefined -``` - -Returns the filter function (either user-defined or automatic, depending on configuration) for the columnId specified. - -## Row API - -### `columnFilters` - -```tsx -columnFilters: Record -``` - -The column filters map for the row. This object tracks whether a row is passing/failing specific filters by their column ID. - -### `columnFiltersMeta` - -```tsx -columnFiltersMeta: Record -``` - -The column filters meta map for the row. This object tracks any filter meta for a row as optionally provided during the filtering process. - -## Table Options - -### `filterFns` - -```tsx -filterFns?: Record -``` - -This option allows you to define custom filter functions that can be referenced in a column's `filterFn` option by their key. -Example: - -```tsx -declare module '@tanstack/[adapter]-table' { - interface FilterFns { - myCustomFilter: FilterFn - } -} - -const column = columnHelper.data('key', { - filterFn: 'myCustomFilter', -}) - -const table = useReactTable({ - columns: [column], - filterFns: { - myCustomFilter: (rows, columnIds, filterValue) => { - // return the filtered rows - }, - }, -}) -``` - -### `filterFromLeafRows` - -```tsx -filterFromLeafRows?: boolean -``` - -By default, filtering is done from parent rows down (so if a parent row is filtered out, all of its children will be filtered out as well). Setting this option to `true` will cause filtering to be done from leaf rows up (which means parent rows will be included so long as one of their child or grand-child rows is also included). - -### `maxLeafRowFilterDepth` - -```tsx -maxLeafRowFilterDepth?: number -``` - -By default, filtering is done for all rows (max depth of 100), no matter if they are root level parent rows or the child leaf rows of a parent row. Setting this option to `0` will cause filtering to only be applied to the root level parent rows, with all sub-rows remaining unfiltered. Similarly, setting this option to `1` will cause filtering to only be applied to child leaf rows 1 level deep, and so on. - -This is useful for situations where you want a row's entire child hierarchy to be visible regardless of the applied filter. - -### `enableFilters` - -```tsx -enableFilters?: boolean -``` - -Enables/disables all filters for the table. - -### `manualFiltering` - -```tsx -manualFiltering?: boolean -``` - -Disables the `getFilteredRowModel` from being used to filter data. This may be useful if your table needs to dynamically support both client-side and server-side filtering. - -### `onColumnFiltersChange` - -```tsx -onColumnFiltersChange?: OnChangeFn -``` - -If provided, this function will be called with an `updaterFn` when `state.columnFilters` changes. This overrides the default internal state management, so you will need to persist the state change either fully or partially outside of the table. - -### `enableColumnFilters` - -```tsx -enableColumnFilters?: boolean -``` - -Enables/disables **all** column filters for the table. - -### `getFilteredRowModel` - -```tsx -getFilteredRowModel?: ( - table: Table -) => () => RowModel -``` - -If provided, this function is called **once** per table and should return a **new function** which will calculate and return the row model for the table when it's filtered. - -- For server-side filtering, this function is unnecessary and can be ignored since the server should already return the filtered row model. -- For client-side filtering, this function is required. A default implementation is provided via any table adapter's `{ getFilteredRowModel }` export. - -Example: - -```tsx -import { getFilteredRowModel } from '@tanstack/[adapter]-table' - - - getFilteredRowModel: getFilteredRowModel(), -}) -``` - -## Table API - -### `setColumnFilters` - -```tsx -setColumnFilters: (updater: Updater) => void -``` - -Sets or updates the `state.columnFilters` state. - -### `resetColumnFilters` - -```tsx -resetColumnFilters: (defaultState?: boolean) => void -``` - -Resets the **columnFilters** state to `initialState.columnFilters`, or `true` can be passed to force a default blank state reset to `[]`. - -### `getPreFilteredRowModel` - -```tsx -getPreFilteredRowModel: () => RowModel -``` - -Returns the row model for the table before any **column** filtering has been applied. - -### `getFilteredRowModel` - -```tsx -getFilteredRowModel: () => RowModel -``` - -Returns the row model for the table after **column** filtering has been applied. diff --git a/docs/api/features/column-ordering.md b/docs/api/features/column-ordering.md deleted file mode 100644 index 37bfb95337..0000000000 --- a/docs/api/features/column-ordering.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -title: Column Ordering APIs -id: column-ordering ---- - -## State - -Column ordering state is stored on the table using the following shape: - -```tsx -export type ColumnOrderTableState = { - columnOrder: ColumnOrderState -} - -export type ColumnOrderState = string[] -``` - -## Table Options - -### `onColumnOrderChange` - -```tsx -onColumnOrderChange?: OnChangeFn -``` - -If provided, this function will be called with an `updaterFn` when `state.columnOrder` changes. This overrides the default internal state management, so you will need to persist the state change either fully or partially outside of the table. - -## Table API - -### `setColumnOrder` - -```tsx -setColumnOrder: (updater: Updater) => void -``` - -Sets or updates the `state.columnOrder` state. - -### `resetColumnOrder` - -```tsx -resetColumnOrder: (defaultState?: boolean) => void -``` - -Resets the **columnOrder** state to `initialState.columnOrder`, or `true` can be passed to force a default blank state reset to `[]`. - -## Column API - -### `getIndex` - -```tsx -getIndex: (position?: ColumnPinningPosition) => number -``` - -Returns the index of the column in the order of the visible columns. Optionally pass a `position` parameter to get the index of the column in a sub-section of the table. - -### `getIsFirstColumn` - -```tsx -getIsFirstColumn: (position?: ColumnPinningPosition) => boolean -``` - -Returns `true` if the column is the first column in the order of the visible columns. Optionally pass a `position` parameter to check if the column is the first in a sub-section of the table. - -### `getIsLastColumn` - -```tsx -getIsLastColumn: (position?: ColumnPinningPosition) => boolean -``` - -Returns `true` if the column is the last column in the order of the visible columns. Optionally pass a `position` parameter to check if the column is the last in a sub-section of the table. \ No newline at end of file diff --git a/docs/api/features/column-pinning.md b/docs/api/features/column-pinning.md deleted file mode 100644 index a312b33823..0000000000 --- a/docs/api/features/column-pinning.md +++ /dev/null @@ -1,266 +0,0 @@ ---- -title: Column Pinning APIs -id: column-pinning ---- - -## Can-Pin - -The ability for a column to be **pinned** is determined by the following: - -- `options.enablePinning` is not set to `false` -- `options.enableColumnPinning` is not set to `false` -- `columnDefinition.enablePinning` is not set to `false` - -## State - -Pinning state is stored on the table using the following shape: - -```tsx -export type ColumnPinningPosition = false | 'left' | 'right' - -export type ColumnPinningState = { - left?: string[] - right?: string[] -} - - -export type ColumnPinningTableState = { - columnPinning: ColumnPinningState -} -``` - -## Table Options - -### `enableColumnPinning` - -```tsx -enableColumnPinning?: boolean -``` - -Enables/disables column pinning for all columns in the table. - -### `onColumnPinningChange` - -```tsx -onColumnPinningChange?: OnChangeFn -``` - -If provided, this function will be called with an `updaterFn` when `state.columnPinning` changes. This overrides the default internal state management, so you will also need to supply `state.columnPinning` from your own managed state. - -## Column Def Options - -### `enablePinning` - -```tsx -enablePinning?: boolean -``` - -Enables/disables pinning for the column. - -## Table API - -### `setColumnPinning` - -```tsx -setColumnPinning: (updater: Updater) => void -``` - -Sets or updates the `state.columnPinning` state. - -### `resetColumnPinning` - -```tsx -resetColumnPinning: (defaultState?: boolean) => void -``` - -Resets the **columnPinning** state to `initialState.columnPinning`, or `true` can be passed to force a default blank state reset to `{ left: [], right: [], }`. - -### `getIsSomeColumnsPinned` - -```tsx -getIsSomeColumnsPinned: (position?: ColumnPinningPosition) => boolean -``` - -Returns whether or not any columns are pinned. Optionally specify to only check for pinned columns in either the `left` or `right` position. - -_Note: Does not account for column visibility_ - -### `getLeftHeaderGroups` - -```tsx -getLeftHeaderGroups: () => HeaderGroup[] -``` - -Returns the left pinned header groups for the table. - -### `getCenterHeaderGroups` - -```tsx -getCenterHeaderGroups: () => HeaderGroup[] -``` - -Returns the unpinned/center header groups for the table. - -### `getRightHeaderGroups` - -```tsx -getRightHeaderGroups: () => HeaderGroup[] -``` - -Returns the right pinned header groups for the table. - -### `getLeftFooterGroups` - -```tsx -getLeftFooterGroups: () => HeaderGroup[] -``` - -Returns the left pinned footer groups for the table. - -### `getCenterFooterGroups` - -```tsx -getCenterFooterGroups: () => HeaderGroup[] -``` - -Returns the unpinned/center footer groups for the table. - -### `getRightFooterGroups` - -```tsx -getRightFooterGroups: () => HeaderGroup[] -``` - -Returns the right pinned footer groups for the table. - -### `getLeftFlatHeaders` - -```tsx -getLeftFlatHeaders: () => Header[] -``` - -Returns a flat array of left pinned headers for the table, including parent headers. - -### `getCenterFlatHeaders` - -```tsx -getCenterFlatHeaders: () => Header[] -``` - -Returns a flat array of unpinned/center headers for the table, including parent headers. - -### `getRightFlatHeaders` - -```tsx -getRightFlatHeaders: () => Header[] -``` - -Returns a flat array of right pinned headers for the table, including parent headers. - -### `getLeftLeafHeaders` - -```tsx -getLeftLeafHeaders: () => Header[] -``` - -Returns a flat array of leaf-node left pinned headers for the table. - -### `getCenterLeafHeaders` - -```tsx -getCenterLeafHeaders: () => Header[] -``` - -Returns a flat array of leaf-node unpinned/center headers for the table. - -### `getRightLeafHeaders` - -```tsx -getRightLeafHeaders: () => Header[] -``` - -Returns a flat array of leaf-node right pinned headers for the table. - -### `getLeftLeafColumns` - -```tsx -getLeftLeafColumns: () => Column[] -``` - -Returns all left pinned leaf columns. - -### `getRightLeafColumns` - -```tsx -getRightLeafColumns: () => Column[] -``` - -Returns all right pinned leaf columns. - -### `getCenterLeafColumns` - -```tsx -getCenterLeafColumns: () => Column[] -``` - -Returns all center pinned (unpinned) leaf columns. - -## Column API - -### `getCanPin` - -```tsx -getCanPin: () => boolean -``` - -Returns whether or not the column can be pinned. - -### `getPinnedIndex` - -```tsx -getPinnedIndex: () => number -``` - -Returns the numeric pinned index of the column within a pinned column group. - -### `getIsPinned` - -```tsx -getIsPinned: () => ColumnPinningPosition -``` - -Returns the pinned position of the column. (`'left'`, `'right'` or `false`) - -### `pin` - -```tsx -pin: (position: ColumnPinningPosition) => void -``` - -Pins a column to the `'left'` or `'right'`, or unpins the column to the center if `false` is passed. - -## Row API - -### `getLeftVisibleCells` - -```tsx -getLeftVisibleCells: () => Cell[] -``` - -Returns all left pinned leaf cells in the row. - -### `getRightVisibleCells` - -```tsx -getRightVisibleCells: () => Cell[] -``` - -Returns all right pinned leaf cells in the row. - -### `getCenterVisibleCells` - -```tsx -getCenterVisibleCells: () => Cell[] -``` - -Returns all center pinned (unpinned) leaf cells in the row. diff --git a/docs/api/features/column-sizing.md b/docs/api/features/column-sizing.md deleted file mode 100644 index 0bf7631be8..0000000000 --- a/docs/api/features/column-sizing.md +++ /dev/null @@ -1,253 +0,0 @@ ---- -title: Column Sizing APIs -id: column-sizing ---- - -## State - -Column sizing state is stored on the table using the following shape: - -```tsx -export type ColumnSizingTableState = { - columnSizing: ColumnSizing - columnSizingInfo: ColumnSizingInfoState -} - -export type ColumnSizing = Record - -export type ColumnSizingInfoState = { - startOffset: null | number - startSize: null | number - deltaOffset: null | number - deltaPercentage: null | number - isResizingColumn: false | string - columnSizingStart: [string, number][] -} -``` - -## Column Def Options - -### `enableResizing` - -```tsx -enableResizing?: boolean -``` - -Enables or disables column resizing for the column. - -### `size` - -```tsx -size?: number -``` - -The desired size for the column - -### `minSize` - -```tsx -minSize?: number -``` - -The minimum allowed size for the column - -### `maxSize` - -```tsx -maxSize?: number -``` - -The maximum allowed size for the column - -## Column API - -### `getSize` - -```tsx -getSize: () => number -``` - -Returns the current size of the column - -### `getStart` - -```tsx -getStart: (position?: ColumnPinningPosition) => number -``` - -Returns the offset measurement along the row-axis (usually the x-axis for standard tables) for the column, measuring the size of all preceding columns. - -Useful for sticky or absolute positioning of columns. (e.g. `left` or `transform`) - -### `getAfter` - -```tsx -getAfter: (position?: ColumnPinningPosition) => number -``` - -Returns the offset measurement along the row-axis (usually the x-axis for standard tables) for the column, measuring the size of all succeeding columns. - -Useful for sticky or absolute positioning of columns. (e.g. `right` or `transform`) - -### `getCanResize` - -```tsx -getCanResize: () => boolean -``` - -Returns `true` if the column can be resized. - -### `getIsResizing` - -```tsx -getIsResizing: () => boolean -``` - -Returns `true` if the column is currently being resized. - -### `resetSize` - -```tsx -resetSize: () => void -``` - -Resets the column size to its initial size. - -## Header API - -### `getSize` - -```tsx -getSize: () => number -``` - -Returns the size for the header, calculated by summing the size of all leaf-columns that belong to it. - -### `getStart` - -```tsx -getStart: (position?: ColumnPinningPosition) => number -``` - -Returns the offset measurement along the row-axis (usually the x-axis for standard tables) for the header. This is effectively a sum of the offset measurements of all preceding headers. - -### `getResizeHandler` - -```tsx -getResizeHandler: () => (event: unknown) => void -``` - -Returns an event handler function that can be used to resize the header. It can be used as an: - -- `onMouseDown` handler -- `onTouchStart` handler - -The dragging and release events are automatically handled for you. - -## Table Options - -### `enableColumnResizing` - -```tsx -enableColumnResizing?: boolean -``` - -Enables/disables column resizing for \*all columns\*\*. - -### `columnResizeMode` - -```tsx -columnResizeMode?: 'onChange' | 'onEnd' -``` - -Determines when the columnSizing state is updated. `onChange` updates the state when the user is dragging the resize handle. `onEnd` updates the state when the user releases the resize handle. - -### `columnResizeDirection` - -```tsx -columnResizeDirection?: 'ltr' | 'rtl' -``` - -Enables or disables right-to-left support for resizing the column. defaults to 'ltr'. - -### `onColumnSizingChange` - -```tsx -onColumnSizingChange?: OnChangeFn -``` - -This optional function will be called when the columnSizing state changes. If you provide this function, you will be responsible for maintaining its state yourself. You can pass this state back to the table via the `state.columnSizing` table option. - -### `onColumnSizingInfoChange` - -```tsx -onColumnSizingInfoChange?: OnChangeFn -``` - -This optional function will be called when the columnSizingInfo state changes. If you provide this function, you will be responsible for maintaining its state yourself. You can pass this state back to the table via the `state.columnSizingInfo` table option. - -## Table API - -### `setColumnSizing` - -```tsx -setColumnSizing: (updater: Updater) => void -``` - -Sets the column sizing state using an updater function or a value. This will trigger the underlying `onColumnSizingChange` function if one is passed to the table options, otherwise the state will be managed automatically by the table. - -### `setColumnSizingInfo` - -```tsx -setColumnSizingInfo: (updater: Updater) => void -``` - -Sets the column sizing info state using an updater function or a value. This will trigger the underlying `onColumnSizingInfoChange` function if one is passed to the table options, otherwise the state will be managed automatically by the table. - -### `resetColumnSizing` - -```tsx -resetColumnSizing: (defaultState?: boolean) => void -``` - -Resets column sizing to its initial state. If `defaultState` is `true`, the default state for the table will be used instead of the initialValue provided to the table. - -### `resetHeaderSizeInfo` - -```tsx -resetHeaderSizeInfo: (defaultState?: boolean) => void -``` - -Resets column sizing info to its initial state. If `defaultState` is `true`, the default state for the table will be used instead of the initialValue provided to the table. - -### `getTotalSize` - -```tsx -getTotalSize: () => number -``` - -Returns the total size of the table by calculating the sum of the sizes of all leaf-columns. - -### `getLeftTotalSize` - -```tsx -getLeftTotalSize: () => number -``` - -If pinning, returns the total size of the left portion of the table by calculating the sum of the sizes of all left leaf-columns. - -### `getCenterTotalSize` - -```tsx -getCenterTotalSize: () => number -``` - -If pinning, returns the total size of the center portion of the table by calculating the sum of the sizes of all unpinned/center leaf-columns. - -### `getRightTotalSize` - -```tsx -getRightTotalSize: () => number -``` - -If pinning, returns the total size of the right portion of the table by calculating the sum of the sizes of all right leaf-columns. diff --git a/docs/api/features/column-visibility.md b/docs/api/features/column-visibility.md deleted file mode 100644 index e1280e7c03..0000000000 --- a/docs/api/features/column-visibility.md +++ /dev/null @@ -1,178 +0,0 @@ ---- -title: Column Visibility APIs -id: column-visibility ---- - -## State - -Column visibility state is stored on the table using the following shape: - -```tsx -export type VisibilityState = Record - -export type VisibilityTableState = { - columnVisibility: VisibilityState -} -``` - -## Column Def Options - -### `enableHiding` - -```tsx -enableHiding?: boolean -``` - -Enables/disables hiding the column - -## Column API - -### `getCanHide` - -```tsx -getCanHide: () => boolean -``` - -Returns whether the column can be hidden - -### `getIsVisible` - -```tsx -getIsVisible: () => boolean -``` - -Returns whether the column is visible - -### `toggleVisibility` - -```tsx -toggleVisibility: (value?: boolean) => void -``` - -Toggles the column visibility - -### `getToggleVisibilityHandler` - -```tsx -getToggleVisibilityHandler: () => (event: unknown) => void -``` - -Returns a function that can be used to toggle the column visibility. This function can be used to bind to an event handler to a checkbox. - -## Table Options - -### `onColumnVisibilityChange` - -```tsx -onColumnVisibilityChange?: OnChangeFn -``` - -If provided, this function will be called with an `updaterFn` when `state.columnVisibility` changes. This overrides the default internal state management, so you will need to persist the state change either fully or partially outside of the table. - -### `enableHiding` - -```tsx -enableHiding?: boolean -``` - -Enables/disables hiding of columns. - -## Table API - -### `getVisibleFlatColumns` - -```tsx -getVisibleFlatColumns: () => Column[] -``` - -Returns a flat array of columns that are visible, including parent columns. - -### `getVisibleLeafColumns` - -```tsx -getVisibleLeafColumns: () => Column[] -``` - -Returns a flat array of leaf-node columns that are visible. - -### `getLeftVisibleLeafColumns` - -```tsx -getLeftVisibleLeafColumns: () => Column[] -``` - -If column pinning, returns a flat array of leaf-node columns that are visible in the left portion of the table. - -### `getRightVisibleLeafColumns` - -```tsx -getRightVisibleLeafColumns: () => Column[] -``` - -If column pinning, returns a flat array of leaf-node columns that are visible in the right portion of the table. - -### `getCenterVisibleLeafColumns` - -```tsx -getCenterVisibleLeafColumns: () => Column[] -``` - -If column pinning, returns a flat array of leaf-node columns that are visible in the unpinned/center portion of the table. - -### `setColumnVisibility` - -```tsx -setColumnVisibility: (updater: Updater) => void -``` - -Updates the column visibility state via an updater function or value - -### `resetColumnVisibility` - -```tsx -resetColumnVisibility: (defaultState?: boolean) => void -``` - -Resets the column visibility state to the initial state. If `defaultState` is provided, the state will be reset to `{}` - -### `toggleAllColumnsVisible` - -```tsx -toggleAllColumnsVisible: (value?: boolean) => void -``` - -Toggles the visibility of all columns - -### `getIsAllColumnsVisible` - -```tsx -getIsAllColumnsVisible: () => boolean -``` - -Returns whether all columns are visible - -### `getIsSomeColumnsVisible` - -```tsx -getIsSomeColumnsVisible: () => boolean -``` - -Returns whether some columns are visible - -### `getToggleAllColumnsVisibilityHandler` - -```tsx -getToggleAllColumnsVisibilityHandler: () => ((event: unknown) => void) -``` - -Returns a handler for toggling the visibility of all columns, meant to be bound to a `input[type=checkbox]` element. - -## Row API - -### `getVisibleCells` - -```tsx -getVisibleCells: () => Cell[] -``` - -Returns an array of cells that account for column visibility for the row. \ No newline at end of file diff --git a/docs/api/features/expanding.md b/docs/api/features/expanding.md deleted file mode 100644 index af7ab0db43..0000000000 --- a/docs/api/features/expanding.md +++ /dev/null @@ -1,208 +0,0 @@ ---- -title: Expanding APIs -id: expanding ---- - -## State - -Expanding state is stored on the table using the following shape: - -```tsx -export type ExpandedState = true | Record - -export type ExpandedTableState = { - expanded: ExpandedState -} -``` - -## Row API - -### `toggleExpanded` - -```tsx -toggleExpanded: (expanded?: boolean) => void -``` - -Toggles the expanded state (or sets it if `expanded` is provided) for the row. - -### `getIsExpanded` - -```tsx -getIsExpanded: () => boolean -``` - -Returns whether the row is expanded. - -### `getIsAllParentsExpanded` - -```tsx -getIsAllParentsExpanded: () => boolean -``` - -Returns whether all parent rows of the row are expanded. - -### `getCanExpand` - -```tsx -getCanExpand: () => boolean -``` - -Returns whether the row can be expanded. - -### `getToggleExpandedHandler` - -```tsx -getToggleExpandedHandler: () => () => void -``` - -Returns a function that can be used to toggle the expanded state of the row. This function can be used to bind to an event handler to a button. - -## Table Options - -### `manualExpanding` - -```tsx -manualExpanding?: boolean -``` - -Enables manual row expansion. If this is set to `true`, `getExpandedRowModel` will not be used to expand rows and you would be expected to perform the expansion in your own data model. This is useful if you are doing server-side expansion. - -### `onExpandedChange` - -```tsx -onExpandedChange?: OnChangeFn -``` - -This function is called when the `expanded` table state changes. If a function is provided, you will be responsible for managing this state on your own. To pass the managed state back to the table, use the `tableOptions.state.expanded` option. - -### `autoResetExpanded` - -```tsx -autoResetExpanded?: boolean -``` - -Enable this setting to automatically reset the expanded state of the table when expanding state changes. - -### `enableExpanding` - -```tsx -enableExpanding?: boolean -``` - -Enable/disable expanding for all rows. - -### `getExpandedRowModel` - -```tsx -getExpandedRowModel?: (table: Table) => () => RowModel -``` - -This function is responsible for returning the expanded row model. If this function is not provided, the table will not expand rows. You can use the default exported `getExpandedRowModel` function to get the expanded row model or implement your own. - -### `getIsRowExpanded` - -```tsx -getIsRowExpanded?: (row: Row) => boolean -``` - -If provided, allows you to override the default behavior of determining whether a row is currently expanded. - -### `getRowCanExpand` - -```tsx -getRowCanExpand?: (row: Row) => boolean -``` - -If provided, allows you to override the default behavior of determining whether a row can be expanded. - -### `paginateExpandedRows` - -```tsx -paginateExpandedRows?: boolean -``` - -If `true` expanded rows will be paginated along with the rest of the table (which means expanded rows may span multiple pages). - -If `false` expanded rows will not be considered for pagination (which means expanded rows will always render on their parents page. This also means more rows will be rendered than the set page size) - -## Table API - -### `setExpanded` - -```tsx -setExpanded: (updater: Updater) => void -``` - -Updates the expanded state of the table via an update function or value - -### `toggleAllRowsExpanded` - -```tsx -toggleAllRowsExpanded: (expanded?: boolean) => void -``` - -Toggles the expanded state for all rows. Optionally, provide a value to set the expanded state to. - -### `resetExpanded` - -```tsx -resetExpanded: (defaultState?: boolean) => void -``` - -Reset the expanded state of the table to the initial state. If `defaultState` is provided, the expanded state will be reset to `{}` - -### `getCanSomeRowsExpand` - -```tsx -getCanSomeRowsExpand: () => boolean -``` - -Returns whether there are any rows that can be expanded. - -### `getToggleAllRowsExpandedHandler` - -```tsx -getToggleAllRowsExpandedHandler: () => (event: unknown) => void -``` - -Returns a handler that can be used to toggle the expanded state of all rows. This handler is meant to be used with an `input[type=checkbox]` element. - -### `getIsSomeRowsExpanded` - -```tsx -getIsSomeRowsExpanded: () => boolean -``` - -Returns whether there are any rows that are currently expanded. - -### `getIsAllRowsExpanded` - -```tsx -getIsAllRowsExpanded: () => boolean -``` - -Returns whether all rows are currently expanded. - -### `getExpandedDepth` - -```tsx -getExpandedDepth: () => number -``` - -Returns the maximum depth of the expanded rows. - -### `getExpandedRowModel` - -```tsx -getExpandedRowModel: () => RowModel -``` - -Returns the row model after expansion has been applied. - -### `getPreExpandedRowModel` - -```tsx -getPreExpandedRowModel: () => RowModel -``` - -Returns the row model before expansion has been applied. diff --git a/docs/api/features/filters.md b/docs/api/features/filters.md deleted file mode 100644 index c7c05fcd73..0000000000 --- a/docs/api/features/filters.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: Filter APIs -id: filters ---- - - - -The Filtering API docs are now split into multiple API doc pages: - -- [Column Faceting](../column-faceting) -- [Global Faceting](../global-faceting) -- [Column Filtering](../column-filtering) -- [Global Filtering](../global-filtering) \ No newline at end of file diff --git a/docs/api/features/global-faceting.md b/docs/api/features/global-faceting.md deleted file mode 100644 index 820df889ff..0000000000 --- a/docs/api/features/global-faceting.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: Global Faceting APIs -id: global-faceting ---- - -## Table API - -### `getGlobalFacetedRowModel` - -```tsx -getGlobalFacetedRowModel: () => RowModel -``` - -Returns the faceted row model for the global filter. - -### `getGlobalFacetedUniqueValues` - -```tsx -getGlobalFacetedUniqueValues: () => Map -``` - -Returns the faceted unique values for the global filter. - -### `getGlobalFacetedMinMaxValues` - -```tsx -getGlobalFacetedMinMaxValues: () => [number, number] -``` - -Returns the faceted min and max values for the global filter. diff --git a/docs/api/features/global-filtering.md b/docs/api/features/global-filtering.md deleted file mode 100644 index 7b48f55ad7..0000000000 --- a/docs/api/features/global-filtering.md +++ /dev/null @@ -1,291 +0,0 @@ ---- -title: Global Filtering APIs -id: global-filtering ---- - -## Can-Filter - -The ability for a column to be **globally** filtered is determined by the following: - -- The column was defined a valid `accessorKey`/`accessorFn`. -- If provided, `options.getColumnCanGlobalFilter` returns `true` for the given column. If it is not provided, the column is assumed to be globally filterable if the value in the first row is a `string` or `number` type. -- `column.enableColumnFilter` is not set to `false` -- `options.enableColumnFilters` is not set to `false` -- `options.enableFilters` is not set to `false` - -## State - -Filter state is stored on the table using the following shape: - -```tsx -export interface GlobalFilterTableState { - globalFilter: any -} -``` - -## Filter Functions - -You can use the same filter functions that are available for column filtering for global filtering. See the [Column Filtering APIs](../column-filtering) to learn more about filter functions. - -#### Using Filter Functions - -Filter functions can be used/referenced/defined by passing the following to `options.globalFilterFn`: - -- A `string` that references a built-in filter function -- A function directly provided to the `options.globalFilterFn` option - -The final list of filter functions available for the `tableOptions.globalFilterFn` options use the following type: - -```tsx -export type FilterFnOption = - | 'auto' - | BuiltInFilterFn - | FilterFn -``` - -#### Filter Meta - -Filtering data can often expose additional information about the data that can be used to aid other future operations on the same data. A good example of this concept is a ranking-system like that of [`match-sorter`](https://github.com/kentcdodds/match-sorter) that simultaneously ranks, filters and sorts data. While utilities like `match-sorter` make a lot of sense for single-dimensional filter+sort tasks, the decoupled filtering/sorting architecture of building a table makes them very difficult and slow to use. - -To make a ranking/filtering/sorting system work with tables, `filterFn`s can optionally mark results with a **filter meta** value that can be used later to sort/group/etc the data to your liking. This is done by calling the `addMeta` function supplied to your custom `filterFn`. - -Below is an example using our own `match-sorter-utils` package (a utility fork of `match-sorter`) to rank, filter, and sort the data - -```tsx -import { sortingFns } from '@tanstack/[adapter]-table' - -import { rankItem, compareItems } from '@tanstack/match-sorter-utils' - -const fuzzyFilter = (row, columnId, value, addMeta) => { - // Rank the item - const itemRank = rankItem(row.getValue(columnId), value) - - // Store the ranking info - addMeta(itemRank) - - // Return if the item should be filtered in/out - return itemRank.passed -} - -const fuzzySort = (rowA, rowB, columnId) => { - let dir = 0 - - // Only sort by rank if the column has ranking information - if (rowA.columnFiltersMeta[columnId]) { - dir = compareItems( - rowA.columnFiltersMeta[columnId]!, - rowB.columnFiltersMeta[columnId]! - ) - } - - // Provide an alphanumeric fallback for when the item ranks are equal - return dir === 0 ? sortingFns.alphanumeric(rowA, rowB, columnId) : dir -} -``` - -## Column Def Options - -### `enableGlobalFilter` - -```tsx -enableGlobalFilter?: boolean -``` - -Enables/disables the **global** filter for this column. - -## Column API - -### `getCanGlobalFilter` - -```tsx -getCanGlobalFilter: () => boolean -``` - -Returns whether or not the column can be **globally** filtered. Set to `false` to disable a column from being scanned during global filtering. - -## Row API - -### `columnFiltersMeta` - -```tsx -columnFiltersMeta: Record -``` - -The column filters meta map for the row. This object tracks any filter meta for a row as optionally provided during the filtering process. - -## Table Options - -### `filterFns` - -```tsx -filterFns?: Record -``` - -This option allows you to define custom filter functions that can be referenced in a column's `filterFn` option by their key. -Example: - -```tsx -declare module '@tanstack/table-core' { - interface FilterFns { - myCustomFilter: FilterFn - } -} - -const column = columnHelper.data('key', { - filterFn: 'myCustomFilter', -}) - -const table = useReactTable({ - columns: [column], - filterFns: { - myCustomFilter: (rows, columnIds, filterValue) => { - // return the filtered rows - }, - }, -}) -``` - -### `filterFromLeafRows` - -```tsx -filterFromLeafRows?: boolean -``` - -By default, filtering is done from parent rows down (so if a parent row is filtered out, all of its children will be filtered out as well). Setting this option to `true` will cause filtering to be done from leaf rows up (which means parent rows will be included so long as one of their child or grand-child rows is also included). - -### `maxLeafRowFilterDepth` - -```tsx -maxLeafRowFilterDepth?: number -``` - -By default, filtering is done for all rows (max depth of 100), no matter if they are root level parent rows or the child leaf rows of a parent row. Setting this option to `0` will cause filtering to only be applied to the root level parent rows, with all sub-rows remaining unfiltered. Similarly, setting this option to `1` will cause filtering to only be applied to child leaf rows 1 level deep, and so on. - -This is useful for situations where you want a row's entire child hierarchy to be visible regardless of the applied filter. - -### `enableFilters` - -```tsx -enableFilters?: boolean -``` - -Enables/disables all filters for the table. - -### `manualFiltering` - -```tsx -manualFiltering?: boolean -``` - -Disables the `getFilteredRowModel` from being used to filter data. This may be useful if your table needs to dynamically support both client-side and server-side filtering. - -### `getFilteredRowModel` - -```tsx -getFilteredRowModel?: ( - table: Table -) => () => RowModel -``` - -If provided, this function is called **once** per table and should return a **new function** which will calculate and return the row model for the table when it's filtered. - -- For server-side filtering, this function is unnecessary and can be ignored since the server should already return the filtered row model. -- For client-side filtering, this function is required. A default implementation is provided via any table adapter's `{ getFilteredRowModel }` export. - -Example: - -```tsx -import { getFilteredRowModel } from '@tanstack/[adapter]-table' - - getFilteredRowModel: getFilteredRowModel(), -}) -``` - -### `globalFilterFn` - -```tsx -globalFilterFn?: FilterFn | keyof FilterFns | keyof BuiltInFilterFns -``` - -The filter function to use for global filtering. - -Options: - -- A `string` referencing a [built-in filter function](#filter-functions)) -- A `string` that references a custom filter functions provided via the `tableOptions.filterFns` option -- A [custom filter function](#filter-functions) - -### `onGlobalFilterChange` - -```tsx -onGlobalFilterChange?: OnChangeFn -``` - -If provided, this function will be called with an `updaterFn` when `state.globalFilter` changes. This overrides the default internal state management, so you will need to persist the state change either fully or partially outside of the table. - -### `enableGlobalFilter` - -```tsx -enableGlobalFilter?: boolean -``` - -Enables/disables the global filter for the table. - -### `getColumnCanGlobalFilter` - -```tsx -getColumnCanGlobalFilter?: (column: Column) => boolean -``` - -If provided, this function will be called with the column and should return `true` or `false` to indicate whether this column should be used for global filtering. -This is useful if the column can contain data that is not `string` or `number` (i.e. `undefined`). - -## Table API - -### `getPreFilteredRowModel` - -```tsx -getPreFilteredRowModel: () => RowModel -``` - -Returns the row model for the table before any **column** filtering has been applied. - -### `getFilteredRowModel` - -```tsx -getFilteredRowModel: () => RowModel -``` - -Returns the row model for the table after **column** filtering has been applied. - -### `setGlobalFilter` - -```tsx -setGlobalFilter: (updater: Updater) => void -``` - -Sets or updates the `state.globalFilter` state. - -### `resetGlobalFilter` - -```tsx -resetGlobalFilter: (defaultState?: boolean) => void -``` - -Resets the **globalFilter** state to `initialState.globalFilter`, or `true` can be passed to force a default blank state reset to `undefined`. - -### `getGlobalAutoFilterFn` - -```tsx -getGlobalAutoFilterFn: (columnId: string) => FilterFn | undefined -``` - -Currently, this function returns the built-in `includesString` filter function. In future releases, it may return more dynamic filter functions based on the nature of the data provided. - -### `getGlobalFilterFn` - -```tsx -getGlobalFilterFn: (columnId: string) => FilterFn | undefined -``` - -Returns the global filter function (either user-defined or automatic, depending on configuration) for the table. diff --git a/docs/api/features/grouping.md b/docs/api/features/grouping.md deleted file mode 100644 index b9c21631fc..0000000000 --- a/docs/api/features/grouping.md +++ /dev/null @@ -1,353 +0,0 @@ ---- -title: Grouping APIs -id: grouping ---- - -## State - -Grouping state is stored on the table using the following shape: - -```tsx -export type GroupingState = string[] - -export type GroupingTableState = { - grouping: GroupingState -} -``` - -## Aggregation Functions - -The following aggregation functions are built-in to the table core: - -- `sum` - - Sums the values of a group of rows -- `min` - - Finds the minimum value of a group of rows -- `max` - - Finds the maximum value of a group of rows -- `extent` - - Finds the minimum and maximum values of a group of rows -- `mean` - - Finds the mean/average value of a group of rows -- `median` - - Finds the median value of a group of rows -- `unique` - - Finds the unique values of a group of rows -- `uniqueCount` - - Finds the number of unique values of a group of rows -- `count` - - Calculates the number of rows in a group - -Every grouping function receives: - -- A function to retrieve the leaf values of the groups rows -- A function to retrieve the immediate-child values of the groups rows - -and should return a value (usually primitive) to build the aggregated row model. - -This is the type signature for every aggregation function: - -```tsx -export type AggregationFn = ( - getLeafRows: () => Row[], - getChildRows: () => Row[] -) => any -``` - -#### Using Aggregation Functions - -Aggregation functions can be used/referenced/defined by passing the following to `columnDefinition.aggregationFn`: - -- A `string` that references a built-in aggregation function -- A `string` that references a custom aggregation functions provided via the `tableOptions.aggregationFns` option -- A function directly provided to the `columnDefinition.aggregationFn` option - -The final list of aggregation functions available for the `columnDef.aggregationFn` use the following type: - -```tsx -export type AggregationFnOption = - | 'auto' - | keyof AggregationFns - | BuiltInAggregationFn - | AggregationFn -``` - -## Column Def Options - -### `aggregationFn` - -```tsx -aggregationFn?: AggregationFn | keyof AggregationFns | keyof BuiltInAggregationFns -``` - -The aggregation function to use with this column. - -Options: - -- A `string` referencing a [built-in aggregation function](#aggregation-functions)) -- A [custom aggregation function](#aggregation-functions) - -### `aggregatedCell` - -```tsx -aggregatedCell?: Renderable< - { - table: Table - row: Row - column: Column - cell: Cell - getValue: () => any - renderValue: () => any - } -> -``` - -The cell to display each row for the column if the cell is an aggregate. If a function is passed, it will be passed a props object with the context of the cell and should return the property type for your adapter (the exact type depends on the adapter being used). - -### `enableGrouping` - -```tsx -enableGrouping?: boolean -``` - -Enables/disables grouping for this column. - -### `getGroupingValue` - -```tsx -getGroupingValue?: (row: TData) => any -``` - -Specify a value to be used for grouping rows on this column. If this option is not specified, the value derived from `accessorKey` / `accessorFn` will be used instead. - -## Column API - -### `aggregationFn` - -```tsx -aggregationFn?: AggregationFnOption -``` - -The resolved aggregation function for the column. - -### `getCanGroup` - -```tsx -getCanGroup: () => boolean -``` - -Returns whether or not the column can be grouped. - -### `getIsGrouped` - -```tsx -getIsGrouped: () => boolean -``` - -Returns whether or not the column is currently grouped. - -### `getGroupedIndex` - -```tsx -getGroupedIndex: () => number -``` - -Returns the index of the column in the grouping state. - -### `toggleGrouping` - -```tsx -toggleGrouping: () => void -``` - -Toggles the grouping state of the column. - -### `getToggleGroupingHandler` - -```tsx -getToggleGroupingHandler: () => () => void -``` - -Returns a function that toggles the grouping state of the column. This is useful for passing to the `onClick` prop of a button. - -### `getAutoAggregationFn` - -```tsx -getAutoAggregationFn: () => AggregationFn | undefined -``` - -Returns the automatically inferred aggregation function for the column. - -### `getAggregationFn` - -```tsx -getAggregationFn: () => AggregationFn | undefined -``` - -Returns the aggregation function for the column. - -## Row API - -### `groupingColumnId` - -```tsx -groupingColumnId?: string -``` - -If this row is grouped, this is the id of the column that this row is grouped by. - -### `groupingValue` - -```tsx -groupingValue?: any -``` - -If this row is grouped, this is the unique/shared value for the `groupingColumnId` for all of the rows in this group. - -### `getIsGrouped` - -```tsx -getIsGrouped: () => boolean -``` - -Returns whether or not the row is currently grouped. - -### `getGroupingValue` - -```tsx -getGroupingValue: (columnId: string) => unknown -``` - -Returns the grouping value for any row and column (including leaf rows). - -## Table Options - -### `aggregationFns` - -```tsx -aggregationFns?: Record -``` - -This option allows you to define custom aggregation functions that can be referenced in a column's `aggregationFn` option by their key. -Example: - -```tsx -declare module '@tanstack/table-core' { - interface AggregationFns { - myCustomAggregation: AggregationFn - } -} - -const column = columnHelper.data('key', { - aggregationFn: 'myCustomAggregation', -}) - -const table = useReactTable({ - columns: [column], - aggregationFns: { - myCustomAggregation: (columnId, leafRows, childRows) => { - // return the aggregated value - }, - }, -}) -``` - -### `manualGrouping` - -```tsx -manualGrouping?: boolean -``` - -Enables manual grouping. If this option is set to `true`, the table will not automatically group rows using `getGroupedRowModel()` and instead will expect you to manually group the rows before passing them to the table. This is useful if you are doing server-side grouping and aggregation. - -### `onGroupingChange` - -```tsx -onGroupingChange?: OnChangeFn -``` - -If this function is provided, it will be called when the grouping state changes and you will be expected to manage the state yourself. You can pass the managed state back to the table via the `tableOptions.state.grouping` option. - -### `enableGrouping` - -```tsx -enableGrouping?: boolean -``` - -Enables/disables grouping for all columns. - -### `getGroupedRowModel` - -```tsx -getGroupedRowModel?: (table: Table) => () => RowModel -``` - -Returns the row model after grouping has taken place, but no further. - -### `groupedColumnMode` - -```tsx -groupedColumnMode?: false | 'reorder' | 'remove' // default: `reorder` -``` - -Grouping columns are automatically reordered by default to the start of the columns list. If you would rather remove them or leave them as-is, set the appropriate mode here. - -## Table API - -### `setGrouping` - -```tsx -setGrouping: (updater: Updater) => void -``` - -Sets or updates the `state.grouping` state. - -### `resetGrouping` - -```tsx -resetGrouping: (defaultState?: boolean) => void -``` - -Resets the **grouping** state to `initialState.grouping`, or `true` can be passed to force a default blank state reset to `[]`. - -### `getPreGroupedRowModel` - -```tsx -getPreGroupedRowModel: () => RowModel -``` - -Returns the row model for the table before any grouping has been applied. - -### `getGroupedRowModel` - -```tsx -getGroupedRowModel: () => RowModel -``` - -Returns the row model for the table after grouping has been applied. - -## Cell API - -### `getIsAggregated` - -```tsx -getIsAggregated: () => boolean -``` - -Returns whether or not the cell is currently aggregated. - -### `getIsGrouped` - -```tsx -getIsGrouped: () => boolean -``` - -Returns whether or not the cell is currently grouped. - -### `getIsPlaceholder` - -```tsx -getIsPlaceholder: () => boolean -``` - -Returns whether or not the cell is currently a placeholder. \ No newline at end of file diff --git a/docs/api/features/pagination.md b/docs/api/features/pagination.md deleted file mode 100644 index 5e80d5a7a2..0000000000 --- a/docs/api/features/pagination.md +++ /dev/null @@ -1,207 +0,0 @@ ---- -title: Pagination APIs -id: pagination ---- - -## State - -Pagination state is stored on the table using the following shape: - -```tsx -export type PaginationState = { - pageIndex: number - pageSize: number -} - -export type PaginationTableState = { - pagination: PaginationState -} - -export type PaginationInitialTableState = { - pagination?: Partial -} -``` - -## Table Options - -### `manualPagination` - -```tsx -manualPagination?: boolean -``` - -Enables manual pagination. If this option is set to `true`, the table will not automatically paginate rows using `getPaginationRowModel()` and instead will expect you to manually paginate the rows before passing them to the table. This is useful if you are doing server-side pagination and aggregation. - -### `pageCount` - -```tsx -pageCount?: number -``` - -When manually controlling pagination, you can supply a total `pageCount` value to the table if you know it. If you do not know how many pages there are, you can set this to `-1`. Alternatively, you can provide a `rowCount` value and the table will calculate the `pageCount` internally. - -### `rowCount` - -```tsx -rowCount?: number -``` - -When manually controlling pagination, you can supply a total `rowCount` value to the table if you know it. `pageCount` will be calculated internally from `rowCount` and `pageSize`. - -### `autoResetPageIndex` - -```tsx -autoResetPageIndex?: boolean -``` - -If set to `true`, pagination will be reset to the first page when page-altering state changes eg. `data` is updated, filters change, grouping changes, etc. - -> 🧠 Note: This option defaults to `false` if `manualPagination` is set to `true` - -### `onPaginationChange` - -```tsx -onPaginationChange?: OnChangeFn -``` - -If this function is provided, it will be called when the pagination state changes and you will be expected to manage the state yourself. You can pass the managed state back to the table via the `tableOptions.state.pagination` option. - -### `getPaginationRowModel` - -```tsx -getPaginationRowModel?: (table: Table) => () => RowModel -``` - -Returns the row model after pagination has taken place, but no further. - -Pagination columns are automatically reordered by default to the start of the columns list. If you would rather remove them or leave them as-is, set the appropriate mode here. - -## Table API - -### `setPagination` - -```tsx -setPagination: (updater: Updater) => void -``` - -Sets or updates the `state.pagination` state. - -### `resetPagination` - -```tsx -resetPagination: (defaultState?: boolean) => void -``` - -Resets the **pagination** state to `initialState.pagination`, or `true` can be passed to force a default blank state reset to `[]`. - -### `setPageIndex` - -```tsx -setPageIndex: (updater: Updater) => void -``` - -Updates the page index using the provided function or value. - -### `resetPageIndex` - -```tsx -resetPageIndex: (defaultState?: boolean) => void -``` - -Resets the page index to its initial state. If `defaultState` is `true`, the page index will be reset to `0` regardless of initial state. - -### `setPageSize` - -```tsx -setPageSize: (updater: Updater) => void -``` - -Updates the page size using the provided function or value. - -### `resetPageSize` - -```tsx -resetPageSize: (defaultState?: boolean) => void -``` - -Resets the page size to its initial state. If `defaultState` is `true`, the page size will be reset to `10` regardless of initial state. - -### `getPageOptions` - -```tsx -getPageOptions: () => number[] -``` - -Returns an array of page options (zero-index-based) for the current page size. - -### `getCanPreviousPage` - -```tsx -getCanPreviousPage: () => boolean -``` - -Returns whether the table can go to the previous page. - -### `getCanNextPage` - -```tsx -getCanNextPage: () => boolean -``` - -Returns whether the table can go to the next page. - -### `previousPage` - -```tsx -previousPage: () => void -``` - -Decrements the page index by one, if possible. - -### `nextPage` - -```tsx -nextPage: () => void -``` - -Increments the page index by one, if possible. - -### `firstPage` - -```tsx -firstPage: () => void -``` - -Sets the page index to `0`. - -### `lastPage` - -```tsx -lastPage: () => void -``` - -Sets the page index to the last available page. - -### `getPageCount` - -```tsx -getPageCount: () => number -``` - -Returns the page count. If manually paginating or controlling the pagination state, this will come directly from the `options.pageCount` table option, otherwise it will be calculated from the table data using the total row count and current page size. - -### `getPrePaginationRowModel` - -```tsx -getPrePaginationRowModel: () => RowModel -``` - -Returns the row model for the table before any pagination has been applied. - -### `getPaginationRowModel` - -```tsx -getPaginationRowModel: () => RowModel -``` - -Returns the row model for the table after pagination has been applied. diff --git a/docs/api/features/pinning.md b/docs/api/features/pinning.md deleted file mode 100644 index 92222ac7fe..0000000000 --- a/docs/api/features/pinning.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: Pinning APIs -id: pinning ---- - - - -The pinning apis are now split into multiple api pages: - -- [Column Pinning](../column-pinning) -- [Row Pinning](../row-pinning) \ No newline at end of file diff --git a/docs/api/features/row-pinning.md b/docs/api/features/row-pinning.md deleted file mode 100644 index 52e46d5628..0000000000 --- a/docs/api/features/row-pinning.md +++ /dev/null @@ -1,138 +0,0 @@ ---- -title: Row Pinning APIs -id: row-pinning ---- - -## Can-Pin - -The ability for a row to be **pinned** is determined by the following: - -- `options.enableRowPinning` resolves to `true` -- `options.enablePinning` is not set to `false` - -## State - -Pinning state is stored on the table using the following shape: - -```tsx -export type RowPinningPosition = false | 'top' | 'bottom' - -export type RowPinningState = { - top?: string[] - bottom?: string[] -} - -export type RowPinningRowState = { - rowPinning: RowPinningState -} -``` - -## Table Options - -### `enableRowPinning` - -```tsx -enableRowPinning?: boolean | ((row: Row) => boolean) -``` - -Enables/disables row pinning for all rows in the table. - -### `keepPinnedRows` - -```tsx -keepPinnedRows?: boolean -``` - -When `false`, pinned rows will not be visible if they are filtered or paginated out of the table. When `true`, pinned rows will always be visible regardless of filtering or pagination. Defaults to `true`. - -### `onRowPinningChange` - -```tsx -onRowPinningChange?: OnChangeFn -``` - -If provided, this function will be called with an `updaterFn` when `state.rowPinning` changes. This overrides the default internal state management, so you will also need to supply `state.rowPinning` from your own managed state. - -## Table API - -### `setRowPinning` - -```tsx -setRowPinning: (updater: Updater) => void -``` - -Sets or updates the `state.rowPinning` state. - -### `resetRowPinning` - -```tsx -resetRowPinning: (defaultState?: boolean) => void -``` - -Resets the **rowPinning** state to `initialState.rowPinning`, or `true` can be passed to force a default blank state reset to `{}`. - -### `getIsSomeRowsPinned` - -```tsx -getIsSomeRowsPinned: (position?: RowPinningPosition) => boolean -``` - -Returns whether or not any rows are pinned. Optionally specify to only check for pinned rows in either the `top` or `bottom` position. - -### `getTopRows` - -```tsx -getTopRows: () => Row[] -``` - -Returns all top pinned rows. - -### `getBottomRows` - -```tsx -getBottomRows: () => Row[] -``` - -Returns all bottom pinned rows. - -### `getCenterRows` - -```tsx -getCenterRows: () => Row[] -``` - -Returns all rows that are not pinned to the top or bottom. - -## Row API - -### `pin` - -```tsx -pin: (position: RowPinningPosition) => void -``` - -Pins a row to the `'top'` or `'bottom'`, or unpins the row to the center if `false` is passed. - -### `getCanPin` - -```tsx -getCanPin: () => boolean -``` - -Returns whether or not the row can be pinned. - -### `getIsPinned` - -```tsx -getIsPinned: () => RowPinningPosition -``` - -Returns the pinned position of the row. (`'top'`, `'bottom'` or `false`) - -### `getPinnedIndex` - -```tsx -getPinnedIndex: () => number -``` - -Returns the numeric pinned index of the row within a pinned row group. \ No newline at end of file diff --git a/docs/api/features/row-selection.md b/docs/api/features/row-selection.md deleted file mode 100644 index 312fde8573..0000000000 --- a/docs/api/features/row-selection.md +++ /dev/null @@ -1,230 +0,0 @@ ---- -title: Row Selection APIs -id: row-selection ---- - -## State - -Row selection state is stored on the table using the following shape: - -```tsx -export type RowSelectionState = Record - -export type RowSelectionTableState = { - rowSelection: RowSelectionState -} -``` - -By default, the row selection state uses the index of each row as the row identifiers. Row selection state can instead be tracked with a custom unique row id by passing in a custom [getRowId](../../../api/core/table.md#getrowid) function to the the table. - -## Table Options - -### `enableRowSelection` - -```tsx -enableRowSelection?: boolean | ((row: Row) => boolean) -``` - -- Enables/disables row selection for all rows in the table OR -- A function that given a row, returns whether to enable/disable row selection for that row - -### `enableMultiRowSelection` - -```tsx -enableMultiRowSelection?: boolean | ((row: Row) => boolean) -``` - -- Enables/disables multiple row selection for all rows in the table OR -- A function that given a row, returns whether to enable/disable multiple row selection for that row's children/grandchildren - -### `enableSubRowSelection` - -```tsx -enableSubRowSelection?: boolean | ((row: Row) => boolean) -``` - -Enables/disables automatic sub-row selection when a parent row is selected, or a function that enables/disables automatic sub-row selection for each row. - -(Use in combination with expanding or grouping features) - -### `onRowSelectionChange` - -```tsx -onRowSelectionChange?: OnChangeFn -``` - -If provided, this function will be called with an `updaterFn` when `state.rowSelection` changes. This overrides the default internal state management, so you will need to persist the state change either fully or partially outside of the table. - -## Table API - -### `getToggleAllRowsSelectedHandler` - -```tsx -getToggleAllRowsSelectedHandler: () => (event: unknown) => void -``` - -Returns a handler that can be used to toggle all rows in the table. - -### `getToggleAllPageRowsSelectedHandler` - -```tsx -getToggleAllPageRowsSelectedHandler: () => (event: unknown) => void -``` - -Returns a handler that can be used to toggle all rows on the current page. - -### `setRowSelection` - -```tsx -setRowSelection: (updater: Updater) => void -``` - -Sets or updates the `state.rowSelection` state. - -### `resetRowSelection` - -```tsx -resetRowSelection: (defaultState?: boolean) => void -``` - -Resets the **rowSelection** state to the `initialState.rowSelection`, or `true` can be passed to force a default blank state reset to `{}`. - -### `getIsAllRowsSelected` - -```tsx -getIsAllRowsSelected: () => boolean -``` - -Returns whether or not all rows in the table are selected. - -### `getIsAllPageRowsSelected` - -```tsx -getIsAllPageRowsSelected: () => boolean -``` - -Returns whether or not all rows on the current page are selected. - -### `getIsSomeRowsSelected` - -```tsx -getIsSomeRowsSelected: () => boolean -``` - -Returns whether or not any rows in the table are selected. - -NOTE: Returns `false` if all rows are selected. - -### `getIsSomePageRowsSelected` - -```tsx -getIsSomePageRowsSelected: () => boolean -``` - -Returns whether or not any rows on the current page are selected. - -### `toggleAllRowsSelected` - -```tsx -toggleAllRowsSelected: (value: boolean) => void -``` - -Selects/deselects all rows in the table. - -### `toggleAllPageRowsSelected` - -```tsx -toggleAllPageRowsSelected: (value: boolean) => void -``` - -Selects/deselects all rows on the current page. - -### `getPreSelectedRowModel` - -```tsx -getPreSelectedRowModel: () => RowModel -``` - -### `getSelectedRowModel` - -```tsx -getSelectedRowModel: () => RowModel -``` - -### `getFilteredSelectedRowModel` - -```tsx -getFilteredSelectedRowModel: () => RowModel -``` - -### `getGroupedSelectedRowModel` - -```tsx -getGroupedSelectedRowModel: () => RowModel -``` - -## Row API - -### `getIsSelected` - -```tsx -getIsSelected: () => boolean -``` - -Returns whether or not the row is selected. - -### `getIsSomeSelected` - -```tsx -getIsSomeSelected: () => boolean -``` - -Returns whether or not some of the row's sub rows are selected. - -### `getIsAllSubRowsSelected` - -```tsx -getIsAllSubRowsSelected: () => boolean -``` - -Returns whether or not all of the row's sub rows are selected. - -### `getCanSelect` - -```tsx -getCanSelect: () => boolean -``` - -Returns whether or not the row can be selected. - -### `getCanMultiSelect` - -```tsx -getCanMultiSelect: () => boolean -``` - -Returns whether or not the row can multi-select. - -### `getCanSelectSubRows` - -```tsx -getCanSelectSubRows: () => boolean -``` - -Returns whether or not the row can select sub rows automatically when the parent row is selected. - -### `toggleSelected` - -```tsx -toggleSelected: (value?: boolean) => void -``` - -Selects/deselects the row. - -### `getToggleSelectedHandler` - -```tsx -getToggleSelectedHandler: () => (event: unknown) => void -``` - -Returns a handler that can be used to toggle the row. diff --git a/docs/api/features/sorting.md b/docs/api/features/sorting.md deleted file mode 100644 index e5b60eea50..0000000000 --- a/docs/api/features/sorting.md +++ /dev/null @@ -1,385 +0,0 @@ ---- -title: Sorting APIs -id: sorting ---- - -## State - -Sorting state is stored on the table using the following shape: - -```tsx -export type SortDirection = 'asc' | 'desc' - -export type ColumnSort = { - id: string - desc: boolean -} - -export type SortingState = ColumnSort[] - -export type SortingTableState = { - sorting: SortingState -} -``` - -## Sorting Functions - -The following sorting functions are built-in to the table core: - -- `alphanumeric` - - Sorts by mixed alphanumeric values without case-sensitivity. Slower, but more accurate if your strings contain numbers that need to be naturally sorted. -- `alphanumericCaseSensitive` - - Sorts by mixed alphanumeric values with case-sensitivity. Slower, but more accurate if your strings contain numbers that need to be naturally sorted. -- `text` - - Sorts by text/string values without case-sensitivity. Faster, but less accurate if your strings contain numbers that need to be naturally sorted. -- `textCaseSensitive` - - Sorts by text/string values with case-sensitivity. Faster, but less accurate if your strings contain numbers that need to be naturally sorted. -- `datetime` - - Sorts by time, use this if your values are `Date` objects. -- `basic` - - Sorts using a basic/standard `a > b ? 1 : a < b ? -1 : 0` comparison. This is the fastest sorting function, but may not be the most accurate. - -Every sorting function receives 2 rows and a column ID and are expected to compare the two rows using the column ID to return `-1`, `0`, or `1` in ascending order. Here's a cheat sheet: - -| Return | Ascending Order | -| ------ | --------------- | -| `-1` | `a < b` | -| `0` | `a === b` | -| `1` | `a > b` | - -This is the type signature for every sorting function: - -```tsx -export type SortingFn = { - (rowA: Row, rowB: Row, columnId: string): number -} -``` - -#### Using Sorting Functions - -Sorting functions can be used/referenced/defined by passing the following to `columnDefinition.sortingFn`: - -- A `string` that references a built-in sorting function -- A `string` that references a custom sorting functions provided via the `tableOptions.sortingFns` option -- A function directly provided to the `columnDefinition.sortingFn` option - -The final list of sorting functions available for the `columnDef.sortingFn` use the following type: - -```tsx -export type SortingFnOption = - | 'auto' - | SortingFns - | BuiltInSortingFns - | SortingFn -``` - -## Column Def Options - -### `sortingFn` - -```tsx -sortingFn?: SortingFn | keyof SortingFns | keyof BuiltInSortingFns -``` - -The sorting function to use with this column. - -Options: - -- A `string` referencing a [built-in sorting function](#sorting-functions)) -- A [custom sorting function](#sorting-functions) - -### `sortDescFirst` - -```tsx -sortDescFirst?: boolean -``` - -Set to `true` for sorting toggles on this column to start in the descending direction. - -### `enableSorting` - -```tsx -enableSorting?: boolean -``` - -Enables/Disables sorting for this column. - -### `enableMultiSort` - -```tsx -enableMultiSort?: boolean -``` - -Enables/Disables multi-sorting for this column. - -### `invertSorting` - -```tsx -invertSorting?: boolean -``` - -Inverts the order of the sorting for this column. This is useful for values that have an inverted best/worst scale where lower numbers are better, eg. a ranking (1st, 2nd, 3rd) or golf-like scoring - -### `sortUndefined` - -```tsx -sortUndefined?: 'first' | 'last' | false | -1 | 1 // defaults to 1 -``` - -- `'first'` - - Undefined values will be pushed to the beginning of the list -- `'last'` - - Undefined values will be pushed to the end of the list -- `false` - - Undefined values will be considered tied and need to be sorted by the next column filter or original index (whichever applies) -- `-1` - - Undefined values will be sorted with higher priority (ascending) (if ascending, undefined will appear on the beginning of the list) -- `1` - - Undefined values will be sorted with lower priority (descending) (if ascending, undefined will appear on the end of the list) - -> NOTE: `'first'` and `'last'` options are new in v8.16.0 - -## Column API - -### `getAutoSortingFn` - -```tsx -getAutoSortingFn: () => SortingFn -``` - -Returns a sorting function automatically inferred based on the columns values. - -### `getAutoSortDir` - -```tsx -getAutoSortDir: () => SortDirection -``` - -Returns a sort direction automatically inferred based on the columns values. - -### `getSortingFn` - -```tsx -getSortingFn: () => SortingFn -``` - -Returns the resolved sorting function to be used for this column - -### `getNextSortingOrder` - -```tsx -getNextSortingOrder: () => SortDirection | false -``` - -Returns the next sorting order. - -### `getCanSort` - -```tsx -getCanSort: () => boolean -``` - -Returns whether this column can be sorted. - -### `getCanMultiSort` - -```tsx -getCanMultiSort: () => boolean -``` - -Returns whether this column can be multi-sorted. - -### `getSortIndex` - -```tsx -getSortIndex: () => number -``` - -Returns the index position of this column's sorting within the sorting state - -### `getIsSorted` - -```tsx -getIsSorted: () => false | SortDirection -``` - -Returns whether this column is sorted. - -### `getFirstSortDir` - -```tsx -getFirstSortDir: () => SortDirection -``` - -Returns the first direction that should be used when sorting this column. - -### `clearSorting` - -```tsx -clearSorting: () => void -``` - -Removes this column from the table's sorting state - -### `toggleSorting` - -```tsx -toggleSorting: (desc?: boolean, isMulti?: boolean) => void -``` - -Toggles this columns sorting state. If `desc` is provided, it will force the sort direction to that value. If `isMulti` is provided, it will additivity multi-sort the column (or toggle it if it is already sorted). - -### `getToggleSortingHandler` - -```tsx -getToggleSortingHandler: () => undefined | ((event: unknown) => void) -``` - -Returns a function that can be used to toggle this column's sorting state. This is useful for attaching a click handler to the column header. - -## Table Options - -### `sortingFns` - -```tsx -sortingFns?: Record -``` - -This option allows you to define custom sorting functions that can be referenced in a column's `sortingFn` option by their key. -Example: - -```tsx -declare module '@tanstack/table-core' { - interface SortingFns { - myCustomSorting: SortingFn - } -} - -const column = columnHelper.data('key', { - sortingFn: 'myCustomSorting', -}) - -const table = useReactTable({ - columns: [column], - sortingFns: { - myCustomSorting: (rowA: any, rowB: any, columnId: any): number => - rowA.getValue(columnId).value < rowB.getValue(columnId).value ? 1 : -1, - }, -}) -``` - -### `manualSorting` - -```tsx -manualSorting?: boolean -``` - -Enables manual sorting for the table. If this is `true`, you will be expected to sort your data before it is passed to the table. This is useful if you are doing server-side sorting. - -### `onSortingChange` - -```tsx -onSortingChange?: OnChangeFn -``` - -If provided, this function will be called with an `updaterFn` when `state.sorting` changes. This overrides the default internal state management, so you will need to persist the state change either fully or partially outside of the table. - -### `enableSorting` - -```tsx -enableSorting?: boolean -``` - -Enables/Disables sorting for the table. - -### `enableSortingRemoval` - -```tsx -enableSortingRemoval?: boolean -``` - -Enables/Disables the ability to remove sorting for the table. -- If `true` then changing sort order will circle like: 'none' -> 'desc' -> 'asc' -> 'none' -> ... -- If `false` then changing sort order will circle like: 'none' -> 'desc' -> 'asc' -> 'desc' -> 'asc' -> ... - -### `enableMultiRemove` - -```tsx -enableMultiRemove?: boolean -``` - -Enables/disables the ability to remove multi-sorts - -### `enableMultiSort` - -```tsx -enableMultiSort?: boolean -``` - -Enables/Disables multi-sorting for the table. - -### `sortDescFirst` - -```tsx -sortDescFirst?: boolean -``` - -If `true`, all sorts will default to descending as their first toggle state. - -### `getSortedRowModel` - -```tsx -getSortedRowModel?: (table: Table) => () => RowModel -``` - -This function is used to retrieve the sorted row model. If using server-side sorting, this function is not required. To use client-side sorting, pass the exported `getSortedRowModel()` from your adapter to your table or implement your own. - -### `maxMultiSortColCount` - -```tsx -maxMultiSortColCount?: number -``` - -Set a maximum number of columns that can be multi-sorted. - -### `isMultiSortEvent` - -```tsx -isMultiSortEvent?: (e: unknown) => boolean -``` - -Pass a custom function that will be used to determine if a multi-sort event should be triggered. It is passed the event from the sort toggle handler and should return `true` if the event should trigger a multi-sort. - -## Table API - -### `setSorting` - -```tsx -setSorting: (updater: Updater) => void -``` - -Sets or updates the `state.sorting` state. - -### `resetSorting` - -```tsx -resetSorting: (defaultState?: boolean) => void -``` - -Resets the **sorting** state to `initialState.sorting`, or `true` can be passed to force a default blank state reset to `[]`. - -### `getPreSortedRowModel` - -```tsx -getPreSortedRowModel: () => RowModel -``` - -Returns the row model for the table before any sorting has been applied. - -### `getSortedRowModel` - -```tsx -getSortedRowModel: () => RowModel -``` - -Returns the row model for the table after sorting has been applied. diff --git a/docs/config.json b/docs/config.json index 506f16ab8f..57621157a1 100644 --- a/docs/config.json +++ b/docs/config.json @@ -9,98 +9,66 @@ { "label": "Getting Started", "children": [ - { - "label": "Introduction", - "to": "introduction" - }, - { - "label": "Overview", - "to": "overview" - }, - { - "label": "Installation", - "to": "installation" - }, - { - "label": "Migrating to V8", - "to": "guide/migrating" - }, - { - "label": "FAQ", - "to": "faq" - } + { "label": "Introduction", "to": "introduction" }, + { "label": "Overview", "to": "overview" }, + { "label": "Installation", "to": "installation" }, + { "label": "Devtools", "to": "devtools" }, + { "label": "FAQ", "to": "faq" } ], "frameworks": [ { "label": "angular", "children": [ - { - "label": "Angular Table Adapter", - "to": "framework/angular/angular-table" - } + { "label": "Angular Table Adapter", "to": "framework/angular/angular-table" }, + { "label": "Migrating to V9", "to": "framework/angular/guide/migrating" }, + { "label": "Rendering components", "to": "framework/angular/guide/rendering" }, + { "label": "Table composition", "to": "framework/angular/guide/table-composition" } ] }, { "label": "lit", "children": [ - { - "label": "Lit Table Adapter", - "to": "framework/lit/lit-table" - } + { "label": "Lit Table Adapter", "to": "framework/lit/lit-table" } ] }, { - "label": "qwik", + "label": "react", "children": [ - { - "label": "Qwik Table Adapter", - "to": "framework/qwik/qwik-table" - } + { "label": "React Table Adapter", "to": "framework/react/react-table" }, + { "label": "createTableHook Guide", "to": "framework/react/guide/create-table-hook" }, + { "label": "Migrating to V9", "to": "framework/react/guide/migrating" }, + { "label": "useLegacyTable Guide", "to": "framework/react/guide/use-legacy-table" } ] }, { - "label": "react", + "label": "preact", "children": [ - { - "label": "React Table Adapter", - "to": "framework/react/react-table" - } + { "label": "Preact Table Adapter", "to": "framework/preact/preact-table" }, + { "label": "createTableHook Guide", "to": "framework/preact/guide/create-table-hook" } ] }, { "label": "solid", "children": [ - { - "label": "Solid Table Adapter", - "to": "framework/solid/solid-table" - } + { "label": "Solid Table Adapter", "to": "framework/solid/solid-table" } ] }, { "label": "svelte", "children": [ - { - "label": "Svelte Table Adapter", - "to": "framework/svelte/svelte-table" - } + { "label": "Svelte Table Adapter", "to": "framework/svelte/svelte-table" } ] }, { "label": "vue", "children": [ - { - "label": "Vue Table Adapter", - "to": "framework/vue/vue-table" - } + { "label": "Vue Table Adapter", "to": "framework/vue/vue-table" } ] }, { "label": "vanilla", "children": [ - { - "label": "Vanilla JS (No Framework)", - "to": "vanilla" - } + { "label": "Vanilla JS (No Framework)", "to": "vanilla" } ] } ] @@ -108,648 +76,1217 @@ { "label": "Core Guides", "children": [ + { "label": "Data", "to": "guide/data" }, + { "label": "Column Defs", "to": "guide/column-defs" }, + { "label": "Table Instance", "to": "guide/tables" }, + { "label": "Row Models", "to": "guide/row-models" }, + { "label": "Rows", "to": "guide/rows" }, + { "label": "Cells", "to": "guide/cells" }, + { "label": "Header Groups", "to": "guide/header-groups" }, + { "label": "Headers", "to": "guide/headers" }, + { "label": "Columns", "to": "guide/columns" } + ], + "frameworks": [ { - "label": "Data", - "to": "guide/data" - }, - { - "label": "Column Defs", - "to": "guide/column-defs" + "label": "angular", + "children": [ + { "label": "Table State", "to": "framework/angular/guide/table-state" } + ] }, { - "label": "Table Instance", - "to": "guide/tables" + "label": "lit", + "children": [ + { "label": "Table State", "to": "framework/lit/guide/table-state" } + ] }, { - "label": "Row Models", - "to": "guide/row-models" + "label": "react", + "children": [ + { "label": "Table State", "to": "framework/react/guide/table-state" } + ] }, { - "label": "Rows", - "to": "guide/rows" + "label": "preact", + "children": [ + { "label": "Table State", "to": "framework/preact/guide/table-state" } + ] }, { - "label": "Cells", - "to": "guide/cells" + "label": "solid", + "children": [ + { "label": "Table State", "to": "framework/solid/guide/table-state" } + ] }, { - "label": "Header Groups", - "to": "guide/header-groups" + "label": "svelte", + "children": [ + { "label": "Table State", "to": "framework/svelte/guide/table-state" } + ] }, { - "label": "Headers", - "to": "guide/headers" + "label": "vue", + "children": [ + { "label": "Table State", "to": "framework/vue/guide/table-state" } + ] }, { - "label": "Columns", - "to": "guide/columns" + "label": "vanilla", + "children": [ + { "label": "Table State", "to": "framework/vanilla/guide/table-state" } + ] } + ] + }, + { + "label": "Feature Guides", + "children": [ + { "label": "Column Ordering", "to": "guide/column-ordering" }, + { "label": "Column Pinning", "to": "guide/column-pinning" }, + { "label": "Column Sizing", "to": "guide/column-sizing" }, + { "label": "Column Resizing", "to": "guide/column-resizing" }, + { "label": "Column Visibility", "to": "guide/column-visibility" }, + { "label": "Column Filtering", "to": "guide/column-filtering" }, + { "label": "Global Filtering", "to": "guide/global-filtering" }, + { "label": "Fuzzy Filtering", "to": "guide/fuzzy-filtering" }, + { "label": "Column Faceting", "to": "guide/column-faceting" }, + { "label": "Global Faceting", "to": "guide/global-faceting" }, + { "label": "Grouping", "to": "guide/grouping" }, + { "label": "Expanding", "to": "guide/expanding" }, + { "label": "Pagination", "to": "guide/pagination" }, + { "label": "Row Pinning", "to": "guide/row-pinning" }, + { "label": "Row Selection", "to": "guide/row-selection" }, + { "label": "Sorting", "to": "guide/sorting" }, + { "label": "Virtualization", "to": "guide/virtualization" }, + { "label": "Custom Features", "to": "guide/custom-features" } + ] + }, + { + "label": "API Reference", + "children": [ + { "label": "Core API Reference", "to": "reference/index" } ], "frameworks": [ { "label": "angular", "children": [ - { - "label": "Table State", - "to": "framework/angular/guide/table-state" - } + { "label": "Angular API Reference", "to": "framework/angular/reference/index" } ] }, { - "label": "lit", - "children": [ - { - "label": "Table State", - "to": "framework/lit/guide/table-state" - } - ] - }, - { - "label": "qwik", + "label": "react", "children": [ - { - "label": "Table State", - "to": "framework/qwik/guide/table-state" - } + { "label": "React API Reference", "to": "framework/react/reference/index" } ] }, { - "label": "react", + "label": "preact", "children": [ - { - "label": "Table State", - "to": "framework/react/guide/table-state" - } + { "label": "Preact API Reference", "to": "framework/preact/reference/index" } ] }, { "label": "solid", "children": [ - { - "label": "Table State", - "to": "framework/solid/guide/table-state" - } + { "label": "Solid API Reference", "to": "framework/solid/reference/index" } ] }, { "label": "svelte", "children": [ - { - "label": "Table State", - "to": "framework/svelte/guide/table-state" - } + { "label": "Svelte API Reference", "to": "framework/svelte/reference/index" } ] }, { "label": "vue", "children": [ - { - "label": "Table State", - "to": "framework/vue/guide/table-state" - } + { "label": "Vue API Reference", "to": "framework/vue/reference/index" } ] }, { - "label": "vanilla", + "label": "lit", "children": [ - { - "label": "Table State", - "to": "framework/vanilla/guide/table-state" - } + { "label": "Lit API Reference", "to": "framework/lit/reference/index" } ] } ] }, { - "label": "Feature Guides", + "collapsible": true, + "defaultCollapsed": true, + "label": "Table API Reference", "children": [ + { "label": "Table", "to": "reference/index/type-aliases/Table" }, + { "label": "TableOptions", "to": "reference/index/type-aliases/TableOptions" }, + { "label": "TableState", "to": "reference/index/type-aliases/TableState" }, + { "label": "TableMeta", "to": "reference/index/interfaces/TableMeta" }, + { "label": "TableFeature", "to": "reference/index/interfaces/TableFeature" }, + { "label": "TableFeatures", "to": "reference/index/interfaces/TableFeatures" }, + { "label": "StockFeatures", "to": "reference/index/interfaces/StockFeatures" }, + { "label": "CoreFeatures", "to": "reference/index/interfaces/CoreFeatures" }, + { "label": "BaseAtoms", "to": "reference/index/type-aliases/BaseAtoms" }, + { "label": "Atoms", "to": "reference/index/type-aliases/Atoms" }, + { "label": "ExternalAtoms", "to": "reference/index/type-aliases/ExternalAtoms" }, + { "label": "constructTable", "to": "reference/index/functions/constructTable" }, + { "label": "tableOptions", "to": "reference/index/functions/tableOptions" }, + { "label": "tableFeatures", "to": "reference/index/functions/tableFeatures" }, + { "label": "getInitialTableState", "to": "reference/index/functions/getInitialTableState" }, + { "label": "OnChangeFn", "to": "reference/index/type-aliases/OnChangeFn" }, + { "label": "Updater", "to": "reference/index/type-aliases/Updater" }, + { "label": "DebugOptions", "to": "reference/index/type-aliases/DebugOptions" } + ], + "frameworks": [ { - "label": "Column Ordering", - "to": "guide/column-ordering" + "label": "react", + "children": [ + { "label": "useTable", "to": "framework/react/reference/index/functions/useTable" }, + { "label": "createTableHook", "to": "framework/react/reference/index/functions/createTableHook" }, + { "label": "ReactTable", "to": "framework/react/reference/index/type-aliases/ReactTable" }, + { "label": "AppReactTable", "to": "framework/react/reference/index/type-aliases/AppReactTable" }, + { "label": "CreateTableHookOptions", "to": "framework/react/reference/index/type-aliases/CreateTableHookOptions" }, + { "label": "Subscribe", "to": "framework/react/reference/index/functions/Subscribe" }, + { "label": "SubscribeProps", "to": "framework/react/reference/index/type-aliases/SubscribeProps" }, + { "label": "FlexRender", "to": "framework/react/reference/index/functions/FlexRender-1" }, + { "label": "flexRender", "to": "framework/react/reference/index/functions/flexRender" }, + { "label": "FlexRenderProps", "to": "framework/react/reference/index/type-aliases/FlexRenderProps" }, + { "label": "Renderable", "to": "framework/react/reference/index/type-aliases/Renderable" } + ] }, { - "label": "Column Pinning", - "to": "guide/column-pinning" + "label": "angular", + "children": [ + { "label": "injectTable", "to": "framework/angular/reference/functions/injectTable" }, + { "label": "createTableHook", "to": "framework/angular/reference/functions/createTableHook" }, + { "label": "AngularTable", "to": "framework/angular/reference/type-aliases/AngularTable" }, + { "label": "AppAngularTable", "to": "framework/angular/reference/type-aliases/AppAngularTable" }, + { "label": "CreateTableHookResult", "to": "framework/angular/reference/type-aliases/CreateTableHookResult" }, + { "label": "CreateTableContextOptions", "to": "framework/angular/reference/type-aliases/CreateTableContextOptions" }, + { "label": "flexRenderComponent", "to": "framework/angular/reference/functions/flexRenderComponent" }, + { "label": "FlexRenderComponent", "to": "framework/angular/reference/interfaces/FlexRenderComponent" }, + { "label": "FlexRenderContent", "to": "framework/angular/reference/type-aliases/FlexRenderContent" }, + { "label": "FlexRenderInputContent", "to": "framework/angular/reference/type-aliases/FlexRenderInputContent" }, + { "label": "FlexRenderComponentProps", "to": "framework/angular/reference/type-aliases/FlexRenderComponentProps" }, + { "label": "FlexRender", "to": "framework/angular/reference/variables/FlexRender" }, + { "label": "FlexRenderDirective", "to": "framework/angular/reference/classes/FlexRenderDirective" }, + { "label": "FlexRenderCell", "to": "framework/angular/reference/classes/FlexRenderCell" }, + { "label": "TanStackTable", "to": "framework/angular/reference/classes/TanStackTable" }, + { "label": "TanStackTableToken", "to": "framework/angular/reference/variables/TanStackTableToken" }, + { "label": "injectTableContext", "to": "framework/angular/reference/functions/injectTableContext" }, + { "label": "AngularTableComputed", "to": "framework/angular/reference/interfaces/AngularTableComputed" } + ] }, { - "label": "Column Sizing", - "to": "guide/column-sizing" + "label": "preact", + "children": [ + { "label": "useTable", "to": "framework/preact/reference/functions/useTable" }, + { "label": "createTableHook", "to": "framework/preact/reference/functions/createTableHook" }, + { "label": "PreactTable", "to": "framework/preact/reference/type-aliases/PreactTable" }, + { "label": "AppPreactTable", "to": "framework/preact/reference/type-aliases/AppPreactTable" }, + { "label": "CreateTableHookOptions", "to": "framework/preact/reference/type-aliases/CreateTableHookOptions" }, + { "label": "Subscribe", "to": "framework/preact/reference/functions/Subscribe" }, + { "label": "SubscribeProps", "to": "framework/preact/reference/type-aliases/SubscribeProps" }, + { "label": "FlexRender", "to": "framework/preact/reference/functions/FlexRender-1" }, + { "label": "flexRender", "to": "framework/preact/reference/functions/flexRender" } + ] }, { - "label": "Column Visibility", - "to": "guide/column-visibility" + "label": "solid", + "children": [ + { "label": "createTable", "to": "framework/solid/reference/functions/createTable" }, + { "label": "createTableHook", "to": "framework/solid/reference/functions/createTableHook" }, + { "label": "SolidTable", "to": "framework/solid/reference/type-aliases/SolidTable" }, + { "label": "AppSolidTable", "to": "framework/solid/reference/type-aliases/AppSolidTable" }, + { "label": "CreateTableHookOptions", "to": "framework/solid/reference/type-aliases/CreateTableHookOptions" }, + { "label": "FlexRender", "to": "framework/solid/reference/functions/FlexRender-1" }, + { "label": "flexRender", "to": "framework/solid/reference/functions/flexRender" } + ] }, { - "label": "Column Filtering", - "to": "guide/column-filtering" + "label": "svelte", + "children": [ + { "label": "createTable", "to": "framework/svelte/reference/functions/createTable" }, + { "label": "createTableHook", "to": "framework/svelte/reference/functions/createTableHook" }, + { "label": "createTableState", "to": "framework/svelte/reference/functions/createTableState" }, + { "label": "SvelteTable", "to": "framework/svelte/reference/type-aliases/SvelteTable" }, + { "label": "AppSvelteTable", "to": "framework/svelte/reference/type-aliases/AppSvelteTable" }, + { "label": "subscribeTable", "to": "framework/svelte/reference/functions/subscribeTable" }, + { "label": "renderComponent", "to": "framework/svelte/reference/functions/renderComponent" }, + { "label": "renderSnippet", "to": "framework/svelte/reference/functions/renderSnippet" }, + { "label": "FlexRender", "to": "framework/svelte/reference/variables/FlexRender" } + ] }, { - "label": "Global Filtering", - "to": "guide/global-filtering" + "label": "vue", + "children": [ + { "label": "useTable", "to": "framework/vue/reference/functions/useTable" }, + { "label": "createTableHook", "to": "framework/vue/reference/functions/createTableHook" }, + { "label": "VueTable", "to": "framework/vue/reference/type-aliases/VueTable" }, + { "label": "AppVueTable", "to": "framework/vue/reference/type-aliases/AppVueTable" }, + { "label": "CreateTableHookOptions", "to": "framework/vue/reference/type-aliases/CreateTableHookOptions" }, + { "label": "FlexRender", "to": "framework/vue/reference/variables/FlexRender" }, + { "label": "flexRender", "to": "framework/vue/reference/functions/flexRender" } + ] }, { - "label": "Fuzzy Filtering", - "to": "guide/fuzzy-filtering" + "label": "lit", + "children": [ + { "label": "TableController", "to": "framework/lit/reference/classes/TableController" }, + { "label": "createTableHook", "to": "framework/lit/reference/functions/createTableHook" }, + { "label": "LitTable", "to": "framework/lit/reference/type-aliases/LitTable" }, + { "label": "AppLitTable", "to": "framework/lit/reference/type-aliases/AppLitTable" }, + { "label": "CreateTableHookOptions", "to": "framework/lit/reference/type-aliases/CreateTableHookOptions" }, + { "label": "FlexRender", "to": "framework/lit/reference/functions/FlexRender-1" }, + { "label": "flexRender", "to": "framework/lit/reference/functions/flexRender" } + ] + } + ] + }, + { + "collapsible": true, + "defaultCollapsed": true, + "label": "Column API Reference", + "children": [ + { "label": "Column", "to": "reference/index/type-aliases/Column" }, + { "label": "ColumnDef", "to": "reference/index/type-aliases/ColumnDef" }, + { "label": "ColumnDefBase", "to": "reference/index/type-aliases/ColumnDefBase" }, + { "label": "AccessorColumnDef", "to": "reference/index/type-aliases/AccessorColumnDef" }, + { "label": "DisplayColumnDef", "to": "reference/index/type-aliases/DisplayColumnDef" }, + { "label": "GroupColumnDef", "to": "reference/index/type-aliases/GroupColumnDef" }, + { "label": "ColumnDefTemplate", "to": "reference/index/type-aliases/ColumnDefTemplate" }, + { "label": "ColumnHelper", "to": "reference/index/type-aliases/ColumnHelper" }, + { "label": "ColumnMeta", "to": "reference/index/interfaces/ColumnMeta" }, + { "label": "createColumnHelper", "to": "reference/index/functions/createColumnHelper" }, + { "label": "constructColumn", "to": "reference/index/functions/constructColumn" }, + { "label": "AccessorFn", "to": "reference/index/type-aliases/AccessorFn" }, + { "label": "DeepKeys", "to": "reference/index/type-aliases/DeepKeys" }, + { "label": "DeepValue", "to": "reference/index/type-aliases/DeepValue" } + ], + "frameworks": [ + { + "label": "react", + "children": [ + { "label": "AppColumnHelper", "to": "framework/react/reference/index/type-aliases/AppColumnHelper" } + ] }, { - "label": "Column Faceting", - "to": "guide/column-faceting" + "label": "angular", + "children": [ + { "label": "AppColumnHelper", "to": "framework/angular/reference/type-aliases/AppColumnHelper" } + ] }, { - "label": "Global Faceting", - "to": "guide/global-faceting" + "label": "preact", + "children": [ + { "label": "AppColumnHelper", "to": "framework/preact/reference/type-aliases/AppColumnHelper" } + ] }, { - "label": "Grouping", - "to": "guide/grouping" + "label": "solid", + "children": [ + { "label": "AppColumnHelper", "to": "framework/solid/reference/type-aliases/AppColumnHelper" } + ] }, { - "label": "Expanding", - "to": "guide/expanding" + "label": "svelte", + "children": [ + { "label": "AppColumnHelper", "to": "framework/svelte/reference/type-aliases/AppColumnHelper" } + ] }, { - "label": "Pagination", - "to": "guide/pagination" + "label": "vue", + "children": [ + { "label": "AppColumnHelper", "to": "framework/vue/reference/type-aliases/AppColumnHelper" } + ] }, { - "label": "Row Pinning", - "to": "guide/row-pinning" + "label": "lit", + "children": [ + { "label": "AppColumnHelper", "to": "framework/lit/reference/type-aliases/AppColumnHelper" } + ] + } + ] + }, + { + "collapsible": true, + "defaultCollapsed": true, + "label": "Row API Reference", + "children": [ + { "label": "Row", "to": "reference/index/type-aliases/Row" }, + { "label": "RowData", "to": "reference/index/type-aliases/RowData" }, + { "label": "RowModel", "to": "reference/index/interfaces/RowModel" }, + { "label": "constructRow", "to": "reference/index/functions/constructRow" }, + { "label": "createCoreRowModel", "to": "reference/index/functions/createCoreRowModel" }, + { "label": "createFilteredRowModel", "to": "reference/index/functions/createFilteredRowModel" }, + { "label": "createSortedRowModel", "to": "reference/index/functions/createSortedRowModel" }, + { "label": "createGroupedRowModel", "to": "reference/index/functions/createGroupedRowModel" }, + { "label": "createExpandedRowModel", "to": "reference/index/functions/createExpandedRowModel" }, + { "label": "createPaginatedRowModel", "to": "reference/index/functions/createPaginatedRowModel" }, + { "label": "createFacetedRowModel", "to": "reference/index/functions/createFacetedRowModel" }, + { "label": "createFacetedMinMaxValues", "to": "reference/index/functions/createFacetedMinMaxValues" }, + { "label": "createFacetedUniqueValues", "to": "reference/index/functions/createFacetedUniqueValues" }, + { "label": "expandRows", "to": "reference/index/functions/expandRows" }, + { "label": "RowModelFns", "to": "reference/index/type-aliases/RowModelFns" }, + { "label": "CreateRowModels", "to": "reference/index/type-aliases/CreateRowModels" }, + { "label": "CachedRowModels", "to": "reference/index/type-aliases/CachedRowModels" } + ] + }, + { + "collapsible": true, + "defaultCollapsed": true, + "label": "Cell API Reference", + "children": [ + { "label": "Cell", "to": "reference/index/type-aliases/Cell" }, + { "label": "CellContext", "to": "reference/index/interfaces/CellContext" }, + { "label": "CellData", "to": "reference/index/type-aliases/CellData" }, + { "label": "constructCell", "to": "reference/index/functions/constructCell" } + ], + "frameworks": [ + { + "label": "react", + "children": [ + { "label": "AppCellContext", "to": "framework/react/reference/index/type-aliases/AppCellContext" } + ] }, { - "label": "Row Selection", - "to": "guide/row-selection" + "label": "angular", + "children": [ + { "label": "AppCellContext", "to": "framework/angular/reference/type-aliases/AppCellContext" } + ] }, { - "label": "Sorting", - "to": "guide/sorting" + "label": "preact", + "children": [ + { "label": "AppCellContext", "to": "framework/preact/reference/type-aliases/AppCellContext" } + ] }, { - "label": "Virtualization", - "to": "guide/virtualization" + "label": "solid", + "children": [ + { "label": "AppCellContext", "to": "framework/solid/reference/type-aliases/AppCellContext" } + ] + }, + { + "label": "svelte", + "children": [ + { "label": "AppCellContext", "to": "framework/svelte/reference/type-aliases/AppCellContext" } + ] }, { - "label": "Custom Features", - "to": "guide/custom-features" + "label": "vue", + "children": [ + { "label": "AppCellContext", "to": "framework/vue/reference/type-aliases/AppCellContext" } + ] + }, + { + "label": "lit", + "children": [ + { "label": "AppCellContext", "to": "framework/lit/reference/type-aliases/AppCellContext" } + ] } ] }, { - "label": "Core APIs", + "collapsible": true, + "defaultCollapsed": true, + "label": "Header API Reference", "children": [ + { "label": "Header", "to": "reference/index/type-aliases/Header" }, + { "label": "HeaderGroup", "to": "reference/index/type-aliases/HeaderGroup" }, + { "label": "HeaderContext", "to": "reference/index/interfaces/HeaderContext" }, + { "label": "constructHeader", "to": "reference/index/functions/constructHeader" }, + { "label": "buildHeaderGroups", "to": "reference/index/functions/buildHeaderGroups" } + ], + "frameworks": [ { - "label": "Column Def", - "to": "api/core/column-def" + "label": "react", + "children": [ + { "label": "AppHeaderContext", "to": "framework/react/reference/index/type-aliases/AppHeaderContext" } + ] }, { - "label": "Table", - "to": "api/core/table" + "label": "angular", + "children": [ + { "label": "AppHeaderContext", "to": "framework/angular/reference/type-aliases/AppHeaderContext" } + ] }, { - "label": "Column", - "to": "api/core/column" + "label": "preact", + "children": [ + { "label": "AppHeaderContext", "to": "framework/preact/reference/type-aliases/AppHeaderContext" } + ] }, { - "label": "Header Group", - "to": "api/core/header-group" + "label": "solid", + "children": [ + { "label": "AppHeaderContext", "to": "framework/solid/reference/type-aliases/AppHeaderContext" } + ] }, { - "label": "Header", - "to": "api/core/header" + "label": "svelte", + "children": [ + { "label": "AppHeaderContext", "to": "framework/svelte/reference/type-aliases/AppHeaderContext" } + ] }, { - "label": "Row", - "to": "api/core/row" + "label": "vue", + "children": [ + { "label": "AppHeaderContext", "to": "framework/vue/reference/type-aliases/AppHeaderContext" } + ] }, { - "label": "Cell", - "to": "api/core/cell" + "label": "lit", + "children": [ + { "label": "AppHeaderContext", "to": "framework/lit/reference/type-aliases/AppHeaderContext" } + ] } ] }, { - "label": "Feature APIs", + "collapsible": true, + "defaultCollapsed": true, + "label": "Features API Reference", + "children": [ + { "label": "stockFeatures", "to": "reference/index/variables/stockFeatures" }, + { "label": "coreFeatures", "to": "reference/index/variables/coreFeatures" }, + { "label": "coreCellsFeature", "to": "reference/index/variables/coreCellsFeature" }, + { "label": "coreColumnsFeature", "to": "reference/index/variables/coreColumnsFeature" }, + { "label": "coreHeadersFeature", "to": "reference/index/variables/coreHeadersFeature" }, + { "label": "coreRowModelsFeature", "to": "reference/index/variables/coreRowModelsFeature" }, + { "label": "coreRowsFeature", "to": "reference/index/variables/coreRowsFeature" }, + { "label": "coreTablesFeature", "to": "reference/index/variables/coreTablesFeature" }, + { "label": "columnFilteringFeature", "to": "reference/index/variables/columnFilteringFeature" }, + { "label": "columnFacetingFeature", "to": "reference/index/variables/columnFacetingFeature" }, + { "label": "columnGroupingFeature", "to": "reference/index/variables/columnGroupingFeature" }, + { "label": "columnOrderingFeature", "to": "reference/index/variables/columnOrderingFeature" }, + { "label": "columnPinningFeature", "to": "reference/index/variables/columnPinningFeature" }, + { "label": "columnResizingFeature", "to": "reference/index/variables/columnResizingFeature" }, + { "label": "columnSizingFeature", "to": "reference/index/variables/columnSizingFeature" }, + { "label": "columnVisibilityFeature", "to": "reference/index/variables/columnVisibilityFeature" }, + { "label": "globalFilteringFeature", "to": "reference/index/variables/globalFilteringFeature" }, + { "label": "rowExpandingFeature", "to": "reference/index/variables/rowExpandingFeature" }, + { "label": "rowPaginationFeature", "to": "reference/index/variables/rowPaginationFeature" }, + { "label": "rowPinningFeature", "to": "reference/index/variables/rowPinningFeature" }, + { "label": "rowSelectionFeature", "to": "reference/index/variables/rowSelectionFeature" }, + { "label": "rowSortingFeature", "to": "reference/index/variables/rowSortingFeature" }, + { "label": "filterFns", "to": "reference/index/variables/filterFns" }, + { "label": "sortFns", "to": "reference/index/variables/sortFns" }, + { "label": "aggregationFns", "to": "reference/index/variables/aggregationFns" }, + { "label": "constructColumnFilteringFeature", "to": "reference/index/functions/constructColumnFilteringFeature" }, + { "label": "constructColumnFacetingFeature", "to": "reference/index/functions/constructColumnFacetingFeature" }, + { "label": "constructColumnGroupingFeature", "to": "reference/index/functions/constructColumnGroupingFeature" }, + { "label": "constructColumnOrderingFeature", "to": "reference/index/functions/constructColumnOrderingFeature" }, + { "label": "constructColumnPinningFeature", "to": "reference/index/functions/constructColumnPinningFeature" }, + { "label": "constructColumnResizingFeature", "to": "reference/index/functions/constructColumnResizingFeature" }, + { "label": "constructColumnSizingFeature", "to": "reference/index/functions/constructColumnSizingFeature" }, + { "label": "constructColumnVisibilityFeature", "to": "reference/index/functions/constructColumnVisibilityFeature" }, + { "label": "constructGlobalFilteringFeature", "to": "reference/index/functions/constructGlobalFilteringFeature" }, + { "label": "constructRowExpandingFeature", "to": "reference/index/functions/constructRowExpandingFeature" }, + { "label": "constructRowPaginationFeature", "to": "reference/index/functions/constructRowPaginationFeature" }, + { "label": "constructRowPinningFeature", "to": "reference/index/functions/constructRowPinningFeature" }, + { "label": "constructRowSelectionFeature", "to": "reference/index/functions/constructRowSelectionFeature" }, + { "label": "constructRowSortingFeature", "to": "reference/index/functions/constructRowSortingFeature" }, + { "label": "ColumnFilter", "to": "reference/index/interfaces/ColumnFilter" }, + { "label": "ColumnPinningState", "to": "reference/index/interfaces/ColumnPinningState" }, + { "label": "ColumnSort", "to": "reference/index/interfaces/ColumnSort" }, + { "label": "PaginationState", "to": "reference/index/interfaces/PaginationState" }, + { "label": "RowPinningState", "to": "reference/index/interfaces/RowPinningState" }, + { "label": "columnResizingState", "to": "reference/index/interfaces/columnResizingState" }, + { "label": "FilterFn", "to": "reference/index/interfaces/FilterFn" }, + { "label": "FilterFns", "to": "reference/index/interfaces/FilterFns" }, + { "label": "FilterMeta", "to": "reference/index/interfaces/FilterMeta" }, + { "label": "SortFn", "to": "reference/index/interfaces/SortFn" }, + { "label": "SortFns", "to": "reference/index/interfaces/SortFns" }, + { "label": "AggregationFns", "to": "reference/index/interfaces/AggregationFns" }, + { "label": "ColumnFiltersState", "to": "reference/index/type-aliases/ColumnFiltersState" }, + { "label": "ColumnOrderState", "to": "reference/index/type-aliases/ColumnOrderState" }, + { "label": "ColumnPinningPosition", "to": "reference/index/type-aliases/ColumnPinningPosition" }, + { "label": "ColumnResizeDirection", "to": "reference/index/type-aliases/ColumnResizeDirection" }, + { "label": "ColumnResizeMode", "to": "reference/index/type-aliases/ColumnResizeMode" }, + { "label": "ColumnSizingState", "to": "reference/index/type-aliases/ColumnSizingState" }, + { "label": "ColumnVisibilityState", "to": "reference/index/type-aliases/ColumnVisibilityState" }, + { "label": "ExpandedState", "to": "reference/index/type-aliases/ExpandedState" }, + { "label": "GroupingState", "to": "reference/index/type-aliases/GroupingState" }, + { "label": "RowPinningPosition", "to": "reference/index/type-aliases/RowPinningPosition" }, + { "label": "RowSelectionState", "to": "reference/index/type-aliases/RowSelectionState" }, + { "label": "SortDirection", "to": "reference/index/type-aliases/SortDirection" }, + { "label": "SortingState", "to": "reference/index/type-aliases/SortingState" }, + { "label": "AggregationFn", "to": "reference/index/type-aliases/AggregationFn" }, + { "label": "AggregationFnOption", "to": "reference/index/type-aliases/AggregationFnOption" }, + { "label": "FilterFnOption", "to": "reference/index/type-aliases/FilterFnOption" }, + { "label": "SortFnOption", "to": "reference/index/type-aliases/SortFnOption" }, + { "label": "BuiltInAggregationFn", "to": "reference/index/type-aliases/BuiltInAggregationFn" }, + { "label": "BuiltInFilterFn", "to": "reference/index/type-aliases/BuiltInFilterFn" }, + { "label": "BuiltInSortFn", "to": "reference/index/type-aliases/BuiltInSortFn" }, + { "label": "filterFn_arrHas", "to": "reference/index/variables/filterFn_arrHas" }, + { "label": "filterFn_arrIncludes", "to": "reference/index/variables/filterFn_arrIncludes" }, + { "label": "filterFn_arrIncludesAll", "to": "reference/index/variables/filterFn_arrIncludesAll" }, + { "label": "filterFn_arrIncludesSome", "to": "reference/index/variables/filterFn_arrIncludesSome" }, + { "label": "filterFn_equals", "to": "reference/index/variables/filterFn_equals" }, + { "label": "filterFn_equalsString", "to": "reference/index/variables/filterFn_equalsString" }, + { "label": "filterFn_equalsStringSensitive", "to": "reference/index/variables/filterFn_equalsStringSensitive" }, + { "label": "filterFn_greaterThan", "to": "reference/index/variables/filterFn_greaterThan" }, + { "label": "filterFn_greaterThanOrEqualTo", "to": "reference/index/variables/filterFn_greaterThanOrEqualTo" }, + { "label": "filterFn_inNumberRange", "to": "reference/index/variables/filterFn_inNumberRange" }, + { "label": "filterFn_includesString", "to": "reference/index/variables/filterFn_includesString" }, + { "label": "filterFn_includesStringSensitive", "to": "reference/index/variables/filterFn_includesStringSensitive" }, + { "label": "filterFn_lessThan", "to": "reference/index/variables/filterFn_lessThan" }, + { "label": "filterFn_lessThanOrEqualTo", "to": "reference/index/variables/filterFn_lessThanOrEqualTo" }, + { "label": "filterFn_weakEquals", "to": "reference/index/variables/filterFn_weakEquals" }, + { "label": "sortFn_alphanumeric", "to": "reference/index/variables/sortFn_alphanumeric" }, + { "label": "sortFn_alphanumericCaseSensitive", "to": "reference/index/variables/sortFn_alphanumericCaseSensitive" }, + { "label": "sortFn_basic", "to": "reference/index/variables/sortFn_basic" }, + { "label": "sortFn_datetime", "to": "reference/index/variables/sortFn_datetime" }, + { "label": "sortFn_text", "to": "reference/index/variables/sortFn_text" }, + { "label": "sortFn_textCaseSensitive", "to": "reference/index/variables/sortFn_textCaseSensitive" }, + { "label": "aggregationFn_count", "to": "reference/index/variables/aggregationFn_count" }, + { "label": "aggregationFn_extent", "to": "reference/index/variables/aggregationFn_extent" }, + { "label": "aggregationFn_max", "to": "reference/index/variables/aggregationFn_max" }, + { "label": "aggregationFn_mean", "to": "reference/index/variables/aggregationFn_mean" }, + { "label": "aggregationFn_median", "to": "reference/index/variables/aggregationFn_median" }, + { "label": "aggregationFn_min", "to": "reference/index/variables/aggregationFn_min" }, + { "label": "aggregationFn_sum", "to": "reference/index/variables/aggregationFn_sum" }, + { "label": "aggregationFn_unique", "to": "reference/index/variables/aggregationFn_unique" }, + { "label": "aggregationFn_uniqueCount", "to": "reference/index/variables/aggregationFn_uniqueCount" } + ] + }, + { + "collapsible": true, + "defaultCollapsed": true, + "label": "Static Functions API Reference", "children": [ + { "label": "cell_getContext", "to": "reference/static-functions/functions/cell_getContext" }, + { "label": "cell_getIsAggregated", "to": "reference/static-functions/functions/cell_getIsAggregated" }, + { "label": "cell_getIsGrouped", "to": "reference/static-functions/functions/cell_getIsGrouped" }, + { "label": "cell_getIsPlaceholder", "to": "reference/static-functions/functions/cell_getIsPlaceholder" }, + { "label": "cell_getValue", "to": "reference/static-functions/functions/cell_getValue" }, + { "label": "cell_renderValue", "to": "reference/static-functions/functions/cell_renderValue" }, + { "label": "column_clearSorting", "to": "reference/static-functions/functions/column_clearSorting" }, + { "label": "column_getAfter", "to": "reference/static-functions/functions/column_getAfter" }, + { "label": "column_getAggregationFn", "to": "reference/static-functions/functions/column_getAggregationFn" }, + { "label": "column_getAutoAggregationFn", "to": "reference/static-functions/functions/column_getAutoAggregationFn" }, + { "label": "column_getAutoFilterFn", "to": "reference/static-functions/functions/column_getAutoFilterFn" }, + { "label": "column_getAutoSortDir", "to": "reference/static-functions/functions/column_getAutoSortDir" }, + { "label": "column_getAutoSortFn", "to": "reference/static-functions/functions/column_getAutoSortFn" }, + { "label": "column_getCanFilter", "to": "reference/static-functions/functions/column_getCanFilter" }, + { "label": "column_getCanGlobalFilter", "to": "reference/static-functions/functions/column_getCanGlobalFilter" }, + { "label": "column_getCanGroup", "to": "reference/static-functions/functions/column_getCanGroup" }, + { "label": "column_getCanHide", "to": "reference/static-functions/functions/column_getCanHide" }, + { "label": "column_getCanMultiSort", "to": "reference/static-functions/functions/column_getCanMultiSort" }, + { "label": "column_getCanPin", "to": "reference/static-functions/functions/column_getCanPin" }, + { "label": "column_getCanResize", "to": "reference/static-functions/functions/column_getCanResize" }, + { "label": "column_getCanSort", "to": "reference/static-functions/functions/column_getCanSort" }, + { "label": "column_getFacetedMinMaxValues", "to": "reference/static-functions/functions/column_getFacetedMinMaxValues" }, + { "label": "column_getFacetedRowModel", "to": "reference/static-functions/functions/column_getFacetedRowModel" }, + { "label": "column_getFacetedUniqueValues", "to": "reference/static-functions/functions/column_getFacetedUniqueValues" }, + { "label": "column_getFilterFn", "to": "reference/static-functions/functions/column_getFilterFn" }, + { "label": "column_getFilterIndex", "to": "reference/static-functions/functions/column_getFilterIndex" }, + { "label": "column_getFilterValue", "to": "reference/static-functions/functions/column_getFilterValue" }, + { "label": "column_getFirstSortDir", "to": "reference/static-functions/functions/column_getFirstSortDir" }, + { "label": "column_getFlatColumns", "to": "reference/static-functions/functions/column_getFlatColumns" }, + { "label": "column_getGroupedIndex", "to": "reference/static-functions/functions/column_getGroupedIndex" }, + { "label": "column_getIndex", "to": "reference/static-functions/functions/column_getIndex" }, + { "label": "column_getIsFiltered", "to": "reference/static-functions/functions/column_getIsFiltered" }, + { "label": "column_getIsFirstColumn", "to": "reference/static-functions/functions/column_getIsFirstColumn" }, + { "label": "column_getIsGrouped", "to": "reference/static-functions/functions/column_getIsGrouped" }, + { "label": "column_getIsLastColumn", "to": "reference/static-functions/functions/column_getIsLastColumn" }, + { "label": "column_getIsPinned", "to": "reference/static-functions/functions/column_getIsPinned" }, + { "label": "column_getIsResizing", "to": "reference/static-functions/functions/column_getIsResizing" }, + { "label": "column_getIsSorted", "to": "reference/static-functions/functions/column_getIsSorted" }, + { "label": "column_getIsVisible", "to": "reference/static-functions/functions/column_getIsVisible" }, + { "label": "column_getLeafColumns", "to": "reference/static-functions/functions/column_getLeafColumns" }, + { "label": "column_getNextSortingOrder", "to": "reference/static-functions/functions/column_getNextSortingOrder" }, + { "label": "column_getPinnedIndex", "to": "reference/static-functions/functions/column_getPinnedIndex" }, + { "label": "column_getSize", "to": "reference/static-functions/functions/column_getSize" }, + { "label": "column_getSortFn", "to": "reference/static-functions/functions/column_getSortFn" }, + { "label": "column_getSortIndex", "to": "reference/static-functions/functions/column_getSortIndex" }, + { "label": "column_getStart", "to": "reference/static-functions/functions/column_getStart" }, + { "label": "column_getToggleGroupingHandler", "to": "reference/static-functions/functions/column_getToggleGroupingHandler" }, + { "label": "column_getToggleSortingHandler", "to": "reference/static-functions/functions/column_getToggleSortingHandler" }, + { "label": "column_getToggleVisibilityHandler", "to": "reference/static-functions/functions/column_getToggleVisibilityHandler" }, + { "label": "column_pin", "to": "reference/static-functions/functions/column_pin" }, + { "label": "column_resetSize", "to": "reference/static-functions/functions/column_resetSize" }, + { "label": "column_setFilterValue", "to": "reference/static-functions/functions/column_setFilterValue" }, + { "label": "column_toggleGrouping", "to": "reference/static-functions/functions/column_toggleGrouping" }, + { "label": "column_toggleSorting", "to": "reference/static-functions/functions/column_toggleSorting" }, + { "label": "column_toggleVisibility", "to": "reference/static-functions/functions/column_toggleVisibility" }, + { "label": "getDefaultColumnFiltersState", "to": "reference/static-functions/functions/getDefaultColumnFiltersState" }, + { "label": "getDefaultColumnOrderState", "to": "reference/static-functions/functions/getDefaultColumnOrderState" }, + { "label": "getDefaultColumnPinningState", "to": "reference/static-functions/functions/getDefaultColumnPinningState" }, + { "label": "getDefaultColumnResizingState", "to": "reference/static-functions/functions/getDefaultColumnResizingState" }, + { "label": "getDefaultColumnSizingColumnDef", "to": "reference/static-functions/functions/getDefaultColumnSizingColumnDef" }, + { "label": "getDefaultColumnSizingState", "to": "reference/static-functions/functions/getDefaultColumnSizingState" }, + { "label": "getDefaultColumnVisibilityState", "to": "reference/static-functions/functions/getDefaultColumnVisibilityState" }, + { "label": "getDefaultExpandedState", "to": "reference/static-functions/functions/getDefaultExpandedState" }, + { "label": "getDefaultGroupingState", "to": "reference/static-functions/functions/getDefaultGroupingState" }, + { "label": "getDefaultPaginationState", "to": "reference/static-functions/functions/getDefaultPaginationState" }, + { "label": "getDefaultRowPinningState", "to": "reference/static-functions/functions/getDefaultRowPinningState" }, + { "label": "getDefaultRowSelectionState", "to": "reference/static-functions/functions/getDefaultRowSelectionState" }, + { "label": "getDefaultSortingState", "to": "reference/static-functions/functions/getDefaultSortingState" }, + { "label": "header_getContext", "to": "reference/static-functions/functions/header_getContext" }, + { "label": "header_getLeafHeaders", "to": "reference/static-functions/functions/header_getLeafHeaders" }, + { "label": "header_getResizeHandler", "to": "reference/static-functions/functions/header_getResizeHandler" }, + { "label": "header_getSize", "to": "reference/static-functions/functions/header_getSize" }, + { "label": "header_getStart", "to": "reference/static-functions/functions/header_getStart" }, + { "label": "isRowSelected", "to": "reference/static-functions/functions/isRowSelected" }, + { "label": "isSubRowSelected", "to": "reference/static-functions/functions/isSubRowSelected" }, + { "label": "isTouchStartEvent", "to": "reference/static-functions/functions/isTouchStartEvent" }, + { "label": "orderColumns", "to": "reference/static-functions/functions/orderColumns" }, + { "label": "passiveEventSupported", "to": "reference/static-functions/functions/passiveEventSupported" }, + { "label": "row_getAllCells", "to": "reference/static-functions/functions/row_getAllCells" }, + { "label": "row_getAllCellsByColumnId", "to": "reference/static-functions/functions/row_getAllCellsByColumnId" }, + { "label": "row_getAllVisibleCells", "to": "reference/static-functions/functions/row_getAllVisibleCells" }, + { "label": "row_getCanExpand", "to": "reference/static-functions/functions/row_getCanExpand" }, + { "label": "row_getCanMultiSelect", "to": "reference/static-functions/functions/row_getCanMultiSelect" }, + { "label": "row_getCanPin", "to": "reference/static-functions/functions/row_getCanPin" }, + { "label": "row_getCanSelect", "to": "reference/static-functions/functions/row_getCanSelect" }, + { "label": "row_getCanSelectSubRows", "to": "reference/static-functions/functions/row_getCanSelectSubRows" }, + { "label": "row_getCenterVisibleCells", "to": "reference/static-functions/functions/row_getCenterVisibleCells" }, + { "label": "row_getGroupingValue", "to": "reference/static-functions/functions/row_getGroupingValue" }, + { "label": "row_getIsAllParentsExpanded", "to": "reference/static-functions/functions/row_getIsAllParentsExpanded" }, + { "label": "row_getIsAllSubRowsSelected", "to": "reference/static-functions/functions/row_getIsAllSubRowsSelected" }, + { "label": "row_getIsExpanded", "to": "reference/static-functions/functions/row_getIsExpanded" }, + { "label": "row_getIsGrouped", "to": "reference/static-functions/functions/row_getIsGrouped" }, + { "label": "row_getIsPinned", "to": "reference/static-functions/functions/row_getIsPinned" }, + { "label": "row_getIsSelected", "to": "reference/static-functions/functions/row_getIsSelected" }, + { "label": "row_getIsSomeSelected", "to": "reference/static-functions/functions/row_getIsSomeSelected" }, + { "label": "row_getLeafRows", "to": "reference/static-functions/functions/row_getLeafRows" }, + { "label": "row_getLeftVisibleCells", "to": "reference/static-functions/functions/row_getLeftVisibleCells" }, + { "label": "row_getParentRow", "to": "reference/static-functions/functions/row_getParentRow" }, + { "label": "row_getParentRows", "to": "reference/static-functions/functions/row_getParentRows" }, + { "label": "row_getPinnedIndex", "to": "reference/static-functions/functions/row_getPinnedIndex" }, + { "label": "row_getRightVisibleCells", "to": "reference/static-functions/functions/row_getRightVisibleCells" }, + { "label": "row_getToggleExpandedHandler", "to": "reference/static-functions/functions/row_getToggleExpandedHandler" }, + { "label": "row_getToggleSelectedHandler", "to": "reference/static-functions/functions/row_getToggleSelectedHandler" }, + { "label": "row_getUniqueValues", "to": "reference/static-functions/functions/row_getUniqueValues" }, + { "label": "row_getValue", "to": "reference/static-functions/functions/row_getValue" }, + { "label": "row_getVisibleCells", "to": "reference/static-functions/functions/row_getVisibleCells" }, + { "label": "row_pin", "to": "reference/static-functions/functions/row_pin" }, + { "label": "row_renderValue", "to": "reference/static-functions/functions/row_renderValue" }, + { "label": "row_toggleExpanded", "to": "reference/static-functions/functions/row_toggleExpanded" }, + { "label": "row_toggleSelected", "to": "reference/static-functions/functions/row_toggleSelected" }, + { "label": "selectRowsFn", "to": "reference/static-functions/functions/selectRowsFn" }, + { "label": "shouldAutoRemoveFilter", "to": "reference/static-functions/functions/shouldAutoRemoveFilter" }, + { "label": "table_autoResetExpanded", "to": "reference/static-functions/functions/table_autoResetExpanded" }, + { "label": "table_autoResetPageIndex", "to": "reference/static-functions/functions/table_autoResetPageIndex" }, + { "label": "table_firstPage", "to": "reference/static-functions/functions/table_firstPage" }, + { "label": "table_getAllColumns", "to": "reference/static-functions/functions/table_getAllColumns" }, + { "label": "table_getAllFlatColumns", "to": "reference/static-functions/functions/table_getAllFlatColumns" }, + { "label": "table_getAllFlatColumnsById", "to": "reference/static-functions/functions/table_getAllFlatColumnsById" }, + { "label": "table_getAllLeafColumns", "to": "reference/static-functions/functions/table_getAllLeafColumns" }, + { "label": "table_getBottomRows", "to": "reference/static-functions/functions/table_getBottomRows" }, + { "label": "table_getCanNextPage", "to": "reference/static-functions/functions/table_getCanNextPage" }, + { "label": "table_getCanPreviousPage", "to": "reference/static-functions/functions/table_getCanPreviousPage" }, + { "label": "table_getCanSomeRowsExpand", "to": "reference/static-functions/functions/table_getCanSomeRowsExpand" }, + { "label": "table_getCenterFlatHeaders", "to": "reference/static-functions/functions/table_getCenterFlatHeaders" }, + { "label": "table_getCenterFooterGroups", "to": "reference/static-functions/functions/table_getCenterFooterGroups" }, + { "label": "table_getCenterHeaderGroups", "to": "reference/static-functions/functions/table_getCenterHeaderGroups" }, + { "label": "table_getCenterLeafColumns", "to": "reference/static-functions/functions/table_getCenterLeafColumns" }, + { "label": "table_getCenterLeafHeaders", "to": "reference/static-functions/functions/table_getCenterLeafHeaders" }, + { "label": "table_getCenterRows", "to": "reference/static-functions/functions/table_getCenterRows" }, + { "label": "table_getCenterTotalSize", "to": "reference/static-functions/functions/table_getCenterTotalSize" }, + { "label": "table_getCenterVisibleLeafColumns", "to": "reference/static-functions/functions/table_getCenterVisibleLeafColumns" }, + { "label": "table_getColumn", "to": "reference/static-functions/functions/table_getColumn" }, + { "label": "table_getCoreRowModel", "to": "reference/static-functions/functions/table_getCoreRowModel" }, + { "label": "table_getDefaultColumnDef", "to": "reference/static-functions/functions/table_getDefaultColumnDef" }, + { "label": "table_getExpandedDepth", "to": "reference/static-functions/functions/table_getExpandedDepth" }, + { "label": "table_getExpandedRowModel", "to": "reference/static-functions/functions/table_getExpandedRowModel" }, + { "label": "table_getFilteredRowModel", "to": "reference/static-functions/functions/table_getFilteredRowModel" }, + { "label": "table_getFilteredSelectedRowModel", "to": "reference/static-functions/functions/table_getFilteredSelectedRowModel" }, + { "label": "table_getFlatHeaders", "to": "reference/static-functions/functions/table_getFlatHeaders" }, + { "label": "table_getFooterGroups", "to": "reference/static-functions/functions/table_getFooterGroups" }, + { "label": "table_getGlobalAutoFilterFn", "to": "reference/static-functions/functions/table_getGlobalAutoFilterFn" }, + { "label": "table_getGlobalFacetedMinMaxValues", "to": "reference/static-functions/functions/table_getGlobalFacetedMinMaxValues" }, + { "label": "table_getGlobalFacetedRowModel", "to": "reference/static-functions/functions/table_getGlobalFacetedRowModel" }, + { "label": "table_getGlobalFacetedUniqueValues", "to": "reference/static-functions/functions/table_getGlobalFacetedUniqueValues" }, + { "label": "table_getGlobalFilterFn", "to": "reference/static-functions/functions/table_getGlobalFilterFn" }, + { "label": "table_getGroupedRowModel", "to": "reference/static-functions/functions/table_getGroupedRowModel" }, + { "label": "table_getGroupedSelectedRowModel", "to": "reference/static-functions/functions/table_getGroupedSelectedRowModel" }, + { "label": "table_getHeaderGroups", "to": "reference/static-functions/functions/table_getHeaderGroups" }, + { "label": "table_getIsAllColumnsVisible", "to": "reference/static-functions/functions/table_getIsAllColumnsVisible" }, + { "label": "table_getIsAllPageRowsSelected", "to": "reference/static-functions/functions/table_getIsAllPageRowsSelected" }, + { "label": "table_getIsAllRowsExpanded", "to": "reference/static-functions/functions/table_getIsAllRowsExpanded" }, + { "label": "table_getIsAllRowsSelected", "to": "reference/static-functions/functions/table_getIsAllRowsSelected" }, + { "label": "table_getIsSomeColumnsPinned", "to": "reference/static-functions/functions/table_getIsSomeColumnsPinned" }, + { "label": "table_getIsSomeColumnsVisible", "to": "reference/static-functions/functions/table_getIsSomeColumnsVisible" }, + { "label": "table_getIsSomePageRowsSelected", "to": "reference/static-functions/functions/table_getIsSomePageRowsSelected" }, + { "label": "table_getIsSomeRowsExpanded", "to": "reference/static-functions/functions/table_getIsSomeRowsExpanded" }, + { "label": "table_getIsSomeRowsPinned", "to": "reference/static-functions/functions/table_getIsSomeRowsPinned" }, + { "label": "table_getIsSomeRowsSelected", "to": "reference/static-functions/functions/table_getIsSomeRowsSelected" }, + { "label": "table_getLeafHeaders", "to": "reference/static-functions/functions/table_getLeafHeaders" }, + { "label": "table_getLeftFlatHeaders", "to": "reference/static-functions/functions/table_getLeftFlatHeaders" }, + { "label": "table_getLeftFooterGroups", "to": "reference/static-functions/functions/table_getLeftFooterGroups" }, + { "label": "table_getLeftHeaderGroups", "to": "reference/static-functions/functions/table_getLeftHeaderGroups" }, + { "label": "table_getLeftLeafColumns", "to": "reference/static-functions/functions/table_getLeftLeafColumns" }, + { "label": "table_getLeftLeafHeaders", "to": "reference/static-functions/functions/table_getLeftLeafHeaders" }, + { "label": "table_getLeftTotalSize", "to": "reference/static-functions/functions/table_getLeftTotalSize" }, + { "label": "table_getLeftVisibleLeafColumns", "to": "reference/static-functions/functions/table_getLeftVisibleLeafColumns" }, + { "label": "table_getOrderColumnsFn", "to": "reference/static-functions/functions/table_getOrderColumnsFn" }, + { "label": "table_getPageCount", "to": "reference/static-functions/functions/table_getPageCount" }, + { "label": "table_getPageOptions", "to": "reference/static-functions/functions/table_getPageOptions" }, + { "label": "table_getPaginatedRowModel", "to": "reference/static-functions/functions/table_getPaginatedRowModel" }, + { "label": "table_getPinnedLeafColumns", "to": "reference/static-functions/functions/table_getPinnedLeafColumns" }, + { "label": "table_getPinnedVisibleLeafColumns", "to": "reference/static-functions/functions/table_getPinnedVisibleLeafColumns" }, + { "label": "table_getPreExpandedRowModel", "to": "reference/static-functions/functions/table_getPreExpandedRowModel" }, + { "label": "table_getPreFilteredRowModel", "to": "reference/static-functions/functions/table_getPreFilteredRowModel" }, + { "label": "table_getPreGroupedRowModel", "to": "reference/static-functions/functions/table_getPreGroupedRowModel" }, + { "label": "table_getPrePaginatedRowModel", "to": "reference/static-functions/functions/table_getPrePaginatedRowModel" }, + { "label": "table_getPreSelectedRowModel", "to": "reference/static-functions/functions/table_getPreSelectedRowModel" }, + { "label": "table_getPreSortedRowModel", "to": "reference/static-functions/functions/table_getPreSortedRowModel" }, + { "label": "table_getRightFlatHeaders", "to": "reference/static-functions/functions/table_getRightFlatHeaders" }, + { "label": "table_getRightFooterGroups", "to": "reference/static-functions/functions/table_getRightFooterGroups" }, + { "label": "table_getRightHeaderGroups", "to": "reference/static-functions/functions/table_getRightHeaderGroups" }, + { "label": "table_getRightLeafColumns", "to": "reference/static-functions/functions/table_getRightLeafColumns" }, + { "label": "table_getRightLeafHeaders", "to": "reference/static-functions/functions/table_getRightLeafHeaders" }, + { "label": "table_getRightTotalSize", "to": "reference/static-functions/functions/table_getRightTotalSize" }, + { "label": "table_getRightVisibleLeafColumns", "to": "reference/static-functions/functions/table_getRightVisibleLeafColumns" }, + { "label": "table_getRow", "to": "reference/static-functions/functions/table_getRow" }, + { "label": "table_getRowCount", "to": "reference/static-functions/functions/table_getRowCount" }, + { "label": "table_getRowId", "to": "reference/static-functions/functions/table_getRowId" }, + { "label": "table_getRowModel", "to": "reference/static-functions/functions/table_getRowModel" }, + { "label": "table_getSelectedRowModel", "to": "reference/static-functions/functions/table_getSelectedRowModel" }, + { "label": "table_getSortedRowModel", "to": "reference/static-functions/functions/table_getSortedRowModel" }, + { "label": "table_getToggleAllColumnsVisibilityHandler", "to": "reference/static-functions/functions/table_getToggleAllColumnsVisibilityHandler" }, + { "label": "table_getToggleAllPageRowsSelectedHandler", "to": "reference/static-functions/functions/table_getToggleAllPageRowsSelectedHandler" }, + { "label": "table_getToggleAllRowsExpandedHandler", "to": "reference/static-functions/functions/table_getToggleAllRowsExpandedHandler" }, + { "label": "table_getToggleAllRowsSelectedHandler", "to": "reference/static-functions/functions/table_getToggleAllRowsSelectedHandler" }, + { "label": "table_getTopRows", "to": "reference/static-functions/functions/table_getTopRows" }, + { "label": "table_getTotalSize", "to": "reference/static-functions/functions/table_getTotalSize" }, + { "label": "table_getVisibleFlatColumns", "to": "reference/static-functions/functions/table_getVisibleFlatColumns" }, + { "label": "table_getVisibleLeafColumns", "to": "reference/static-functions/functions/table_getVisibleLeafColumns" }, + { "label": "table_lastPage", "to": "reference/static-functions/functions/table_lastPage" }, + { "label": "table_mergeOptions", "to": "reference/static-functions/functions/table_mergeOptions" }, + { "label": "table_nextPage", "to": "reference/static-functions/functions/table_nextPage" }, + { "label": "table_previousPage", "to": "reference/static-functions/functions/table_previousPage" }, + { "label": "table_reset", "to": "reference/static-functions/functions/table_reset" }, + { "label": "table_resetColumnFilters", "to": "reference/static-functions/functions/table_resetColumnFilters" }, + { "label": "table_resetColumnOrder", "to": "reference/static-functions/functions/table_resetColumnOrder" }, + { "label": "table_resetColumnPinning", "to": "reference/static-functions/functions/table_resetColumnPinning" }, + { "label": "table_resetColumnSizing", "to": "reference/static-functions/functions/table_resetColumnSizing" }, + { "label": "table_resetColumnVisibility", "to": "reference/static-functions/functions/table_resetColumnVisibility" }, + { "label": "table_resetExpanded", "to": "reference/static-functions/functions/table_resetExpanded" }, + { "label": "table_resetGlobalFilter", "to": "reference/static-functions/functions/table_resetGlobalFilter" }, + { "label": "table_resetGrouping", "to": "reference/static-functions/functions/table_resetGrouping" }, + { "label": "table_resetHeaderSizeInfo", "to": "reference/static-functions/functions/table_resetHeaderSizeInfo" }, + { "label": "table_resetPageIndex", "to": "reference/static-functions/functions/table_resetPageIndex" }, + { "label": "table_resetPageSize", "to": "reference/static-functions/functions/table_resetPageSize" }, + { "label": "table_resetPagination", "to": "reference/static-functions/functions/table_resetPagination" }, + { "label": "table_resetRowPinning", "to": "reference/static-functions/functions/table_resetRowPinning" }, + { "label": "table_resetRowSelection", "to": "reference/static-functions/functions/table_resetRowSelection" }, + { "label": "table_resetSorting", "to": "reference/static-functions/functions/table_resetSorting" }, + { "label": "table_setColumnFilters", "to": "reference/static-functions/functions/table_setColumnFilters" }, + { "label": "table_setColumnOrder", "to": "reference/static-functions/functions/table_setColumnOrder" }, + { "label": "table_setColumnPinning", "to": "reference/static-functions/functions/table_setColumnPinning" }, + { "label": "table_setColumnResizing", "to": "reference/static-functions/functions/table_setColumnResizing" }, + { "label": "table_setColumnSizing", "to": "reference/static-functions/functions/table_setColumnSizing" }, + { "label": "table_setColumnVisibility", "to": "reference/static-functions/functions/table_setColumnVisibility" }, + { "label": "table_setExpanded", "to": "reference/static-functions/functions/table_setExpanded" }, + { "label": "table_setGlobalFilter", "to": "reference/static-functions/functions/table_setGlobalFilter" }, + { "label": "table_setGrouping", "to": "reference/static-functions/functions/table_setGrouping" }, + { "label": "table_setOptions", "to": "reference/static-functions/functions/table_setOptions" }, + { "label": "table_setPageIndex", "to": "reference/static-functions/functions/table_setPageIndex" }, + { "label": "table_setPageSize", "to": "reference/static-functions/functions/table_setPageSize" }, + { "label": "table_setPagination", "to": "reference/static-functions/functions/table_setPagination" }, + { "label": "table_setRowPinning", "to": "reference/static-functions/functions/table_setRowPinning" }, + { "label": "table_setRowSelection", "to": "reference/static-functions/functions/table_setRowSelection" }, + { "label": "table_setSorting", "to": "reference/static-functions/functions/table_setSorting" }, + { "label": "table_syncExternalStateToBaseAtoms", "to": "reference/static-functions/functions/table_syncExternalStateToBaseAtoms" }, + { "label": "table_toggleAllColumnsVisible", "to": "reference/static-functions/functions/table_toggleAllColumnsVisible" }, + { "label": "table_toggleAllPageRowsSelected", "to": "reference/static-functions/functions/table_toggleAllPageRowsSelected" }, + { "label": "table_toggleAllRowsExpanded", "to": "reference/static-functions/functions/table_toggleAllRowsExpanded" }, + { "label": "table_toggleAllRowsSelected", "to": "reference/static-functions/functions/table_toggleAllRowsSelected" } + ] + }, + { + "collapsible": true, + "defaultCollapsed": true, + "label": "Legacy API Reference", + "children": [], + "frameworks": [ + { + "label": "react", + "children": [ + { "label": "Legacy API Overview", "to": "framework/react/reference/legacy/index" }, + { "label": "useLegacyTable", "to": "framework/react/reference/legacy/functions/useLegacyTable" }, + { "label": "legacyCreateColumnHelper", "to": "framework/react/reference/legacy/functions/legacyCreateColumnHelper" }, + { "label": "getCoreRowModel", "to": "framework/react/reference/legacy/functions/getCoreRowModel" }, + { "label": "getFilteredRowModel", "to": "framework/react/reference/legacy/functions/getFilteredRowModel" }, + { "label": "getSortedRowModel", "to": "framework/react/reference/legacy/functions/getSortedRowModel" }, + { "label": "getGroupedRowModel", "to": "framework/react/reference/legacy/functions/getGroupedRowModel" }, + { "label": "getExpandedRowModel", "to": "framework/react/reference/legacy/functions/getExpandedRowModel" }, + { "label": "getPaginationRowModel", "to": "framework/react/reference/legacy/functions/getPaginationRowModel" }, + { "label": "getFacetedRowModel", "to": "framework/react/reference/legacy/functions/getFacetedRowModel" }, + { "label": "getFacetedMinMaxValues", "to": "framework/react/reference/legacy/functions/getFacetedMinMaxValues" }, + { "label": "getFacetedUniqueValues", "to": "framework/react/reference/legacy/functions/getFacetedUniqueValues" }, + { "label": "LegacyRowModelOptions", "to": "framework/react/reference/legacy/interfaces/LegacyRowModelOptions" }, + { "label": "LegacyTable", "to": "framework/react/reference/legacy/type-aliases/LegacyTable" }, + { "label": "LegacyTableOptions", "to": "framework/react/reference/legacy/type-aliases/LegacyTableOptions" }, + { "label": "LegacyReactTable", "to": "framework/react/reference/legacy/type-aliases/LegacyReactTable" }, + { "label": "LegacyColumnDef", "to": "framework/react/reference/legacy/type-aliases/LegacyColumnDef" }, + { "label": "LegacyColumn", "to": "framework/react/reference/legacy/type-aliases/LegacyColumn" }, + { "label": "LegacyRow", "to": "framework/react/reference/legacy/type-aliases/LegacyRow" }, + { "label": "LegacyCell", "to": "framework/react/reference/legacy/type-aliases/LegacyCell" }, + { "label": "LegacyHeader", "to": "framework/react/reference/legacy/type-aliases/LegacyHeader" }, + { "label": "LegacyHeaderGroup", "to": "framework/react/reference/legacy/type-aliases/LegacyHeaderGroup" } + ] + } + ] + }, + { + "label": "Basic Examples", + "children": [], + "frameworks": [ { - "label": "Column Filtering", - "to": "api/features/column-filtering" + "label": "angular", + "children": [ + { "label": "Basic (Inject Table)", "to": "framework/angular/examples/basic-inject-table" }, + { "label": "Basic (App Table)", "to": "framework/angular/examples/basic-app-table" } + ] }, { - "label": "Column Faceting", - "to": "api/features/column-faceting" + "label": "lit", + "children": [ + { "label": "Basic (TableController)", "to": "framework/lit/examples/basic-table-controller" }, + { "label": "Basic (useAppTable)", "to": "framework/lit/examples/basic-app-table" }, + { "label": "Basic (External State)", "to": "framework/lit/examples/basic-external-state" }, + { "label": "Basic (External Atoms)", "to": "framework/lit/examples/basic-external-atoms" }, + { "label": "Header Groups", "to": "framework/lit/examples/column-groups" } + ] }, { - "label": "Column Ordering", - "to": "api/features/column-ordering" + "label": "react", + "children": [ + { "label": "Basic (useTable)", "to": "framework/react/examples/basic-use-table" }, + { "label": "Basic (useAppTable)", "to": "framework/react/examples/basic-use-app-table" }, + { "label": "Basic (useLegacyTable)", "to": "framework/react/examples/basic-use-legacy-table" }, + { "label": "Basic (External State)", "to": "framework/react/examples/basic-external-state" }, + { "label": "Basic (External Atoms)", "to": "framework/react/examples/basic-external-atoms" }, + { "label": "Basic (Subscribe)", "to": "framework/react/examples/basic-subscribe" }, + { "label": "Header Groups", "to": "framework/react/examples/column-groups" } + ] }, { - "label": "Column Pinning", - "to": "api/features/column-pinning" + "label": "solid", + "children": [ + { "label": "Basic (createTable)", "to": "framework/solid/examples/basic-use-table" }, + { "label": "Basic (createAppTable)", "to": "framework/solid/examples/basic-app-table" }, + { "label": "Basic (External State)", "to": "framework/solid/examples/basic-external-state" }, + { "label": "Basic (External Atoms)", "to": "framework/solid/examples/basic-external-atoms" }, + { "label": "Header Groups", "to": "framework/solid/examples/column-groups" } + ] }, { - "label": "Column Sizing", - "to": "api/features/column-sizing" + "label": "svelte", + "children": [ + { "label": "Basic (createTable)", "to": "framework/svelte/examples/basic-create-table" }, + { "label": "Basic (createAppTable)", "to": "framework/svelte/examples/basic-app-table" }, + { "label": "Basic (Snippets)", "to": "framework/svelte/examples/basic-snippets" }, + { "label": "Basic (External State)", "to": "framework/svelte/examples/basic-external-state" }, + { "label": "Basic (External Atoms)", "to": "framework/svelte/examples/basic-external-atoms" }, + { "label": "Header Groups", "to": "framework/svelte/examples/column-groups" } + ] }, { - "label": "Column Visibility", - "to": "api/features/column-visibility" + "label": "vue", + "children": [ + { "label": "Basic (useTable)", "to": "framework/vue/examples/basic-use-table" }, + { "label": "Basic (useAppTable)", "to": "framework/vue/examples/basic-use-app-table" }, + { "label": "Basic (External State)", "to": "framework/vue/examples/basic-external-state" }, + { "label": "Basic (External Atoms)", "to": "framework/vue/examples/basic-external-atoms" }, + { "label": "Header Groups", "to": "framework/vue/examples/column-groups" } + ] }, { - "label": "Global Faceting", - "to": "api/features/global-faceting" + "label": "preact", + "children": [ + { "label": "Basic (useTable)", "to": "framework/preact/examples/basic-use-table" }, + { "label": "Basic (useAppTable)", "to": "framework/preact/examples/basic-use-app-table" }, + { "label": "Basic (External State)", "to": "framework/preact/examples/basic-external-state" }, + { "label": "Basic (External Atoms)", "to": "framework/preact/examples/basic-external-atoms" }, + { "label": "Basic (Subscribe)", "to": "framework/preact/examples/basic-subscribe" }, + { "label": "Header Groups", "to": "framework/preact/examples/column-groups" } + ] }, { - "label": "Global Filtering", - "to": "api/features/global-filtering" + "label": "vanilla", + "children": [ + { "label": "Basic", "to": "framework/vanilla/examples/basic" } + ] + } + ] + }, + { + "label": "Feature Examples", + "children": [], + "frameworks": [ + { + "label": "angular", + "children": [ + { "label": "Column Filters", "to": "framework/angular/examples/filters" }, + { "label": "Column Ordering", "to": "framework/angular/examples/column-ordering" }, + { "label": "Column Pinning", "to": "framework/angular/examples/column-pinning" }, + { "label": "Column Pinning (Sticky)", "to": "framework/angular/examples/column-pinning-sticky" }, + { "label": "Column Resizing", "to": "framework/angular/examples/column-resizing-performant" }, + { "label": "Column Visibility", "to": "framework/angular/examples/column-visibility" }, + { "label": "Row Expanding", "to": "framework/angular/examples/expanding" }, + { "label": "Column Grouping", "to": "framework/angular/examples/grouping" }, + { "label": "Row Selection", "to": "framework/angular/examples/row-selection" }, + { "label": "Row Selection (Signal)", "to": "framework/angular/examples/row-selection-signal" } + ] }, { - "label": "Sorting", - "to": "api/features/sorting" + "label": "lit", + "children": [ + { "label": "Column Filters", "to": "framework/lit/examples/filters" }, + { "label": "Column Filters (Faceted)", "to": "framework/lit/examples/filters-faceted" }, + { "label": "Fuzzy Search Filters", "to": "framework/lit/examples/filters-fuzzy" }, + { "label": "Column Ordering", "to": "framework/lit/examples/column-ordering" }, + { "label": "Column Pinning", "to": "framework/lit/examples/column-pinning" }, + { "label": "Column Pinning (Split)", "to": "framework/lit/examples/column-pinning-split" }, + { "label": "Sticky Column Pinning", "to": "framework/lit/examples/column-pinning-sticky" }, + { "label": "Column Sizing", "to": "framework/lit/examples/column-sizing" }, + { "label": "Column Resizing", "to": "framework/lit/examples/column-resizing" }, + { "label": "Performant Column Resizing", "to": "framework/lit/examples/column-resizing-performant" }, + { "label": "Column Visibility", "to": "framework/lit/examples/column-visibility" }, + { "label": "Expanding", "to": "framework/lit/examples/expanding" }, + { "label": "Grouping", "to": "framework/lit/examples/grouping" }, + { "label": "Pagination", "to": "framework/lit/examples/pagination" }, + { "label": "Row Pinning", "to": "framework/lit/examples/row-pinning" }, + { "label": "Row Selection", "to": "framework/lit/examples/row-selection" }, + { "label": "Sorting", "to": "framework/lit/examples/sorting" }, + { "label": "Sorting (Dynamic Data)", "to": "framework/lit/examples/sorting-dynamic-data" } + ] }, { - "label": "Grouping", - "to": "api/features/grouping" + "label": "react", + "children": [ + { "label": "Column Filters", "to": "framework/react/examples/filters" }, + { "label": "Column Filters (Faceted)", "to": "framework/react/examples/filters-faceted" }, + { "label": "Fuzzy Search Filters", "to": "framework/react/examples/filters-fuzzy" }, + { "label": "Column Ordering", "to": "framework/react/examples/column-ordering" }, + { "label": "Column Ordering (DnD)", "to": "framework/react/examples/column-dnd" }, + { "label": "Column Pinning", "to": "framework/react/examples/column-pinning" }, + { "label": "Column Pinning (Split)", "to": "framework/react/examples/column-pinning-split" }, + { "label": "Sticky Column Pinning", "to": "framework/react/examples/column-pinning-sticky" }, + { "label": "Column Sizing", "to": "framework/react/examples/column-sizing" }, + { "label": "Column Resizing", "to": "framework/react/examples/column-resizing" }, + { "label": "Performant Column Resizing", "to": "framework/react/examples/column-resizing-performant" }, + { "label": "Column Visibility", "to": "framework/react/examples/column-visibility" }, + { "label": "Expanding", "to": "framework/react/examples/expanding" }, + { "label": "Grouping", "to": "framework/react/examples/grouping" }, + { "label": "Pagination", "to": "framework/react/examples/pagination" }, + { "label": "Row DnD", "to": "framework/react/examples/row-dnd" }, + { "label": "Row Pinning", "to": "framework/react/examples/row-pinning" }, + { "label": "Row Selection", "to": "framework/react/examples/row-selection" }, + { "label": "Sorting", "to": "framework/react/examples/sorting" } + ] }, { - "label": "Expanding", - "to": "api/features/expanding" + "label": "solid", + "children": [ + { "label": "Column Filters", "to": "framework/solid/examples/filters" }, + { "label": "Column Filters (Faceted)", "to": "framework/solid/examples/filters-faceted" }, + { "label": "Fuzzy Search Filters", "to": "framework/solid/examples/filters-fuzzy" }, + { "label": "Column Ordering", "to": "framework/solid/examples/column-ordering" }, + { "label": "Column Pinning", "to": "framework/solid/examples/column-pinning" }, + { "label": "Column Pinning (Split)", "to": "framework/solid/examples/column-pinning-split" }, + { "label": "Sticky Column Pinning", "to": "framework/solid/examples/column-pinning-sticky" }, + { "label": "Column Sizing", "to": "framework/solid/examples/column-sizing" }, + { "label": "Column Resizing", "to": "framework/solid/examples/column-resizing" }, + { "label": "Performant Column Resizing", "to": "framework/solid/examples/column-resizing-performant" }, + { "label": "Column Visibility", "to": "framework/solid/examples/column-visibility" }, + { "label": "Expanding", "to": "framework/solid/examples/expanding" }, + { "label": "Grouping", "to": "framework/solid/examples/grouping" }, + { "label": "Pagination", "to": "framework/solid/examples/pagination" }, + { "label": "Row Pinning", "to": "framework/solid/examples/row-pinning" }, + { "label": "Row Selection", "to": "framework/solid/examples/row-selection" }, + { "label": "Sorting", "to": "framework/solid/examples/sorting" } + ] }, { - "label": "Pagination", - "to": "api/features/pagination" + "label": "svelte", + "children": [ + { "label": "Column Filters", "to": "framework/svelte/examples/filtering" }, + { "label": "Column Filters (Faceted)", "to": "framework/svelte/examples/filters-faceted" }, + { "label": "Fuzzy Search Filters", "to": "framework/svelte/examples/filters-fuzzy" }, + { "label": "Column Ordering", "to": "framework/svelte/examples/column-ordering" }, + { "label": "Column Pinning", "to": "framework/svelte/examples/column-pinning" }, + { "label": "Column Pinning (Split)", "to": "framework/svelte/examples/column-pinning-split" }, + { "label": "Sticky Column Pinning", "to": "framework/svelte/examples/column-pinning-sticky" }, + { "label": "Column Sizing", "to": "framework/svelte/examples/column-sizing" }, + { "label": "Column Resizing", "to": "framework/svelte/examples/column-resizing" }, + { "label": "Performant Column Resizing", "to": "framework/svelte/examples/column-resizing-performant" }, + { "label": "Column Visibility", "to": "framework/svelte/examples/column-visibility" }, + { "label": "Expanding", "to": "framework/svelte/examples/expanding" }, + { "label": "Grouping", "to": "framework/svelte/examples/grouping" }, + { "label": "Pagination", "to": "framework/svelte/examples/pagination" }, + { "label": "Row Pinning", "to": "framework/svelte/examples/row-pinning" }, + { "label": "Row Selection", "to": "framework/svelte/examples/row-selection" }, + { "label": "Sorting", "to": "framework/svelte/examples/sorting" } + ] }, { - "label": "Row Pinning", - "to": "api/features/row-pinning" + "label": "vue", + "children": [ + { "label": "Column Filters", "to": "framework/vue/examples/filters" }, + { "label": "Column Filters (Faceted)", "to": "framework/vue/examples/filters-faceted" }, + { "label": "Fuzzy Search Filters", "to": "framework/vue/examples/filters-fuzzy" }, + { "label": "Column Ordering", "to": "framework/vue/examples/column-ordering" }, + { "label": "Column Pinning", "to": "framework/vue/examples/column-pinning" }, + { "label": "Column Pinning (Split)", "to": "framework/vue/examples/column-pinning-split" }, + { "label": "Sticky Column Pinning", "to": "framework/vue/examples/column-pinning-sticky" }, + { "label": "Column Sizing", "to": "framework/vue/examples/column-sizing" }, + { "label": "Column Resizing", "to": "framework/vue/examples/column-resizing" }, + { "label": "Performant Column Resizing", "to": "framework/vue/examples/column-resizing-performant" }, + { "label": "Column Visibility", "to": "framework/vue/examples/column-visibility" }, + { "label": "Expanding", "to": "framework/vue/examples/expanding" }, + { "label": "Grouping", "to": "framework/vue/examples/grouping" }, + { "label": "Pagination", "to": "framework/vue/examples/pagination" }, + { "label": "Row Pinning", "to": "framework/vue/examples/row-pinning" }, + { "label": "Row Selection", "to": "framework/vue/examples/row-selection" }, + { "label": "Sorting", "to": "framework/vue/examples/sorting" } + ] }, { - "label": "Row Selection", - "to": "api/features/row-selection" - } - ] - }, - { - "label": "Enterprise", - "children": [ + "label": "preact", + "children": [ + { "label": "Column Filters", "to": "framework/preact/examples/filters" }, + { "label": "Column Filters (Faceted)", "to": "framework/preact/examples/filters-faceted" }, + { "label": "Fuzzy Search Filters", "to": "framework/preact/examples/filters-fuzzy" }, + { "label": "Column Ordering", "to": "framework/preact/examples/column-ordering" }, + { "label": "Column Pinning", "to": "framework/preact/examples/column-pinning" }, + { "label": "Column Pinning (Split)", "to": "framework/preact/examples/column-pinning-split" }, + { "label": "Sticky Column Pinning", "to": "framework/preact/examples/column-pinning-sticky" }, + { "label": "Column Sizing", "to": "framework/preact/examples/column-sizing" }, + { "label": "Column Resizing", "to": "framework/preact/examples/column-resizing" }, + { "label": "Performant Column Resizing", "to": "framework/preact/examples/column-resizing-performant" }, + { "label": "Column Visibility", "to": "framework/preact/examples/column-visibility" }, + { "label": "Expanding", "to": "framework/preact/examples/expanding" }, + { "label": "Grouping", "to": "framework/preact/examples/grouping" }, + { "label": "Pagination", "to": "framework/preact/examples/pagination" }, + { "label": "Row Pinning", "to": "framework/preact/examples/row-pinning" }, + { "label": "Row Selection", "to": "framework/preact/examples/row-selection" }, + { "label": "Sorting", "to": "framework/preact/examples/sorting" } + ] + }, { - "label": "AG Grid", - "to": "enterprise/ag-grid" + "label": "vanilla", + "children": [ + { "label": "Pagination", "to": "framework/vanilla/examples/pagination" }, + { "label": "Sorting", "to": "framework/vanilla/examples/sorting" } + ] } ] }, { - "label": "Examples", + "label": "Specialized Examples", "children": [], "frameworks": [ { "label": "angular", "children": [ - { - "to": "framework/angular/examples/basic", - "label": "Basic" - }, - { - "to": "framework/angular/examples/grouping", - "label": "Column Grouping" - }, - { - "to": "framework/angular/examples/column-ordering", - "label": "Column Ordering" - }, - { - "to": "framework/angular/examples/column-pinning", - "label": "Column Pinning" - }, - { - "to": "framework/angular/examples/column-pinning-sticky", - "label": "Sticky Column Pinning" - }, - { - "to": "framework/angular/examples/column-visibility", - "label": "Column Visibility" - }, - { - "to": "framework/angular/examples/filters", - "label": "Column Filters" - }, - { - "to": "framework/angular/examples/row-selection", - "label": "Row Selection" - }, - { - "to": "framework/angular/examples/signal-input", - "label": "Signal Input" - } + { "label": "Composable Tables", "to": "framework/angular/examples/composable-tables" }, + { "label": "Custom Plugin", "to": "framework/angular/examples/custom-plugin" }, + { "label": "Sub Components", "to": "framework/angular/examples/sub-components" }, + { "label": "Fetch API data (SPA / SSR)", "to": "framework/angular/examples/remote-data" }, + { "label": "Signal Input", "to": "framework/angular/examples/signal-input" }, + { "label": "Editable data", "to": "framework/angular/examples/editable" }, + { "label": "Row Drag & Drop", "to": "framework/angular/examples/row-dnd" } ] }, { "label": "lit", "children": [ - { - "to": "framework/lit/examples/basic", - "label": "Basic" - }, - { - "to": "framework/lit/examples/column-sizing", - "label": "Column Sizing" - }, - { - "to": "framework/lit/examples/filters", - "label": "Filters" - }, - { - "to": "framework/lit/examples/row-selection", - "label": "Row Selection" - }, - { - "to": "framework/lit/examples/sorting", - "label": "Sorting" - }, - { - "to": "framework/lit/examples/virtualized-rows", - "label": "Virtualized Rows" - } - ] - }, - { - "label": "qwik", - "children": [ - { - "to": "framework/qwik/examples/basic", - "label": "Basic" - }, - { - "to": "framework/qwik/examples/filters", - "label": "Filters" - }, - { - "to": "framework/qwik/examples/row-selection", - "label": "Row Selection" - }, - { - "to": "framework/qwik/examples/sorting", - "label": "Sorting" - } + { "label": "Composable Tables", "to": "framework/lit/examples/composable-tables" }, + { "label": "Sub Components", "to": "framework/lit/examples/sub-components" }, + { "label": "With TanStack Virtual - Columns", "to": "framework/lit/examples/virtualized-columns" }, + { "label": "With TanStack Virtual - Rows", "to": "framework/lit/examples/virtualized-rows" }, + { "label": "With TanStack Virtual - Infinite Scrolling", "to": "framework/lit/examples/virtualized-infinite-scrolling" } ] }, { "label": "react", "children": [ - { - "to": "framework/react/examples/basic", - "label": "Basic" - }, - { - "to": "framework/react/examples/column-groups", - "label": "Header Groups" - }, - { - "to": "framework/react/examples/filters", - "label": "Column Filters" - }, - { - "to": "framework/react/examples/filters-faceted", - "label": "Column Filters (Faceted)" - }, - { - "to": "framework/react/examples/filters-fuzzy", - "label": "Fuzzy Search Filters" - }, - { - "to": "framework/react/examples/column-ordering", - "label": "Column Ordering" - }, - { - "to": "framework/react/examples/column-dnd", - "label": "Column Ordering (DnD)" - }, - { - "to": "framework/react/examples/column-pinning", - "label": "Column Pinning" - }, - { - "to": "framework/react/examples/column-pinning-sticky", - "label": "Sticky Column Pinning" - }, - { - "to": "framework/react/examples/column-sizing", - "label": "Column Sizing" - }, - { - "to": "framework/react/examples/column-resizing-performant", - "label": "Performant Column Resizing" - }, - { - "to": "framework/react/examples/column-visibility", - "label": "Column Visibility" - }, - { - "to": "framework/react/examples/editable-data", - "label": "Editable Data" - }, - { - "to": "framework/react/examples/expanding", - "label": "Expanding" - }, - { - "to": "framework/react/examples/sub-components", - "label": "Sub Components" - }, - { - "to": "framework/react/examples/fully-controlled", - "label": "Fully Controlled" - }, - { - "to": "framework/react/examples/grouping", - "label": "Grouping" - }, - { - "to": "framework/react/examples/pagination", - "label": "Pagination" - }, - { - "to": "framework/react/examples/pagination-controlled", - "label": "Pagination Controlled" - }, - { - "to": "framework/react/examples/row-dnd", - "label": "Row DnD" - }, - { - "to": "framework/react/examples/row-pinning", - "label": "Row Pinning" - }, - { - "to": "framework/react/examples/row-selection", - "label": "Row Selection" - }, - { - "to": "framework/react/examples/sorting", - "label": "Sorting" - }, - { - "to": "framework/react/examples/virtualized-columns", - "label": "Virtualized Columns" - }, - { - "to": "framework/react/examples/virtualized-rows", - "label": "Virtualized Rows" - }, - { - "to": "framework/react/examples/virtualized-infinite-scrolling", - "label": "Virtualized Infinite Scrolling" - }, - { - "to": "framework/react/examples/kitchen-sink", - "label": "Kitchen Sink" - }, - { - "to": "framework/react/examples/bootstrap", - "label": "React Bootstrap" - }, - { - "to": "framework/react/examples/material-ui-pagination", - "label": "Material UI Pagination" - }, - { - "to": "framework/react/examples/full-width-table", - "label": "React Full Width" - }, - { - "to": "framework/react/examples/full-width-resizable-table", - "label": "React Full Width Resizable" - }, - { - "to": "framework/react/examples/custom-features", - "label": "Custom Features" - }, - { - "to": "framework/react/examples/query-router-search-params", - "label": "Query Router Search Params" - } + { "label": "Composable Tables", "to": "framework/react/examples/composable-tables" }, + { "label": "Custom Plugin", "to": "framework/react/examples/custom-plugin" }, + { "label": "Sub Components", "to": "framework/react/examples/sub-components" }, + { "label": "With TanStack Virtual - Columns", "to": "framework/react/examples/virtualized-columns" }, + { "label": "With TanStack Virtual - Columns (Exp)", "to": "framework/react/examples/virtualized-columns-experimental" }, + { "label": "With TanStack Virtual - Rows", "to": "framework/react/examples/virtualized-rows" }, + { "label": "With TanStack Virtual - Rows (Exp)", "to": "framework/react/examples/virtualized-rows-experimental" }, + { "label": "With TanStack Virtual - Infinite Scrolling", "to": "framework/react/examples/virtualized-infinite-scrolling" }, + { "label": "With TanStack Form", "to": "framework/react/examples/with-tanstack-form" }, + { "label": "With TanStack Query", "to": "framework/react/examples/with-tanstack-query" }, + { "label": "With TanStack Router", "to": "framework/react/examples/with-tanstack-router" } ] }, { "label": "solid", "children": [ - { - "to": "framework/solid/examples/basic", - "label": "Basic" - }, - { - "to": "framework/solid/examples/column-groups", - "label": "Column Groups" - }, - { - "to": "framework/solid/examples/column-ordering", - "label": "Column Ordering" - }, - { - "to": "framework/solid/examples/column-visibility", - "label": "Column Visibility" - }, - { - "to": "framework/solid/examples/filters", - "label": "Filters" - }, - { - "to": "framework/solid/examples/sorting", - "label": "Sorting" - }, - { - "to": "framework/solid/examples/bootstrap", - "label": "Solid Bootstrap" - } + { "label": "Composable Tables", "to": "framework/solid/examples/composable-tables" }, + { "label": "Sub Components", "to": "framework/solid/examples/sub-components" }, + { "label": "With TanStack Virtual - Columns", "to": "framework/solid/examples/virtualized-columns" }, + { "label": "With TanStack Virtual - Rows", "to": "framework/solid/examples/virtualized-rows" }, + { "label": "With TanStack Virtual - Infinite Scrolling", "to": "framework/solid/examples/virtualized-infinite-scrolling" }, + { "label": "With TanStack Form", "to": "framework/solid/examples/with-tanstack-form" }, + { "label": "With TanStack Query", "to": "framework/solid/examples/with-tanstack-query" }, + { "label": "With TanStack Router", "to": "framework/solid/examples/with-tanstack-router" } ] }, { "label": "svelte", "children": [ - { - "to": "framework/svelte/examples/basic", - "label": "Basic" - }, - { - "to": "framework/svelte/examples/column-groups", - "label": "Column Groups" - }, - { - "to": "framework/svelte/examples/column-ordering", - "label": "Column Ordering" - }, - { - "to": "framework/svelte/examples/column-pinning", - "label": "Column Pinning" - }, - { - "to": "framework/svelte/examples/column-visibility", - "label": "Column Visibility" - }, - { - "to": "framework/svelte/examples/filtering", - "label": "Filtering" - }, - { - "to": "framework/svelte/examples/sorting", - "label": "Sorting" - } + { "label": "Composable Tables", "to": "framework/svelte/examples/composable-tables" }, + { "label": "Sub Components", "to": "framework/svelte/examples/sub-components" }, + { "label": "With TanStack Virtual - Columns", "to": "framework/svelte/examples/virtualized-columns" }, + { "label": "With TanStack Virtual - Rows", "to": "framework/svelte/examples/virtualized-rows" }, + { "label": "With TanStack Virtual - Infinite Scrolling", "to": "framework/svelte/examples/virtualized-infinite-scrolling" }, + { "label": "With TanStack Form", "to": "framework/svelte/examples/with-tanstack-form" }, + { "label": "With TanStack Query", "to": "framework/svelte/examples/with-tanstack-query" } ] }, { "label": "vue", "children": [ - { - "to": "framework/vue/examples/basic", - "label": "Basic" - }, - { - "to": "framework/vue/examples/column-ordering", - "label": "Column Ordering" - }, - { - "to": "framework/vue/examples/column-pinning", - "label": "Column Pinning" - }, - { - "to": "framework/vue/examples/pagination", - "label": "Pagination" - }, - { - "to": "framework/vue/examples/row-selection", - "label": "Row Selection" - }, - { - "to": "framework/vue/examples/sorting", - "label": "Sorting" - }, - { - "to": "framework/vue/examples/sub-components", - "label": "Sub Components" - }, - { - "to": "framework/vue/examples/filters", - "label": "Column Filters" - }, - { - "to": "framework/vue/examples/virtualized-rows", - "label": "Virtualized Rows" - } + { "label": "Composable Tables", "to": "framework/vue/examples/composable-tables" }, + { "label": "Sub Components", "to": "framework/vue/examples/sub-components" }, + { "label": "With TanStack Virtual - Columns", "to": "framework/vue/examples/virtualized-columns" }, + { "label": "With TanStack Virtual - Rows", "to": "framework/vue/examples/virtualized-rows" }, + { "label": "With TanStack Virtual - Infinite Scrolling", "to": "framework/vue/examples/virtualized-infinite-scrolling" }, + { "label": "With TanStack Form", "to": "framework/vue/examples/with-tanstack-form" }, + { "label": "With TanStack Query", "to": "framework/vue/examples/with-tanstack-query" } ] }, { - "label": "vanilla", + "label": "preact", + "children": [ + { "label": "Composable Tables", "to": "framework/preact/examples/composable-tables" }, + { "label": "Custom Plugin", "to": "framework/preact/examples/custom-plugin" }, + { "label": "Sub Components", "to": "framework/preact/examples/sub-components" }, + { "label": "With TanStack Query", "to": "framework/preact/examples/with-tanstack-query" } + ] + } + ] + }, + { + "label": "Component Library Examples", + "children": [], + "frameworks": [ + { + "label": "react", "children": [ - { - "to": "framework/vanilla/examples/basic", - "label": "Basic" - }, - { - "to": "framework/vanilla/examples/pagination", - "label": "Pagination" - }, - { - "to": "framework/vanilla/examples/sorting", - "label": "Sorting" - } + { "label": "Basic - Shadcn (Radix)", "to": "framework/react/examples/lib-shadcn-radix" }, + { "label": "Basic - Shadcn (Base UI)", "to": "framework/react/examples/lib-shadcn-base" }, + { "label": "Basic - React Aria", "to": "framework/react/examples/lib-react-aria" }, + { "label": "Basic - Hero UI", "to": "framework/react/examples/lib-hero-ui" }, + { "label": "Basic - Material UI", "to": "framework/react/examples/lib-material-ui" }, + { "label": "Basic - Mantine", "to": "framework/react/examples/lib-mantine" }, + { "label": "Kitchen Sink - Shadcn (Radix)", "to": "framework/react/examples/kitchen-sink-shadcn-radix" }, + { "label": "Kitchen Sink - Shadcn (Base UI)", "to": "framework/react/examples/kitchen-sink-shadcn-base" }, + { "label": "Kitchen Sink - React Aria", "to": "framework/react/examples/kitchen-sink-react-aria" }, + { "label": "Kitchen Sink - Hero UI", "to": "framework/react/examples/kitchen-sink-hero-ui" }, + { "label": "Kitchen Sink - Material UI", "to": "framework/react/examples/kitchen-sink-material-ui" }, + { "label": "Kitchen Sink - Mantine", "to": "framework/react/examples/kitchen-sink-mantine" }, + { "label": "Material React Table", "to": "framework/react/examples/material-react-table" }, + { "label": "Mantine React Table", "to": "framework/react/examples/mantine-react-table" } ] } ] diff --git a/docs/devtools.md b/docs/devtools.md new file mode 100644 index 0000000000..b5674020c4 --- /dev/null +++ b/docs/devtools.md @@ -0,0 +1,207 @@ +--- +title: Devtools +id: devtools +--- + +TanStack Table provides framework-specific devtools adapters that plug into the [TanStack Devtools](https://tanstack.com/devtools) multi-panel UI. + +The table devtools let you inspect registered table instances, switch between multiple tables, and inspect features, state, options, rows, and columns in real time. + +> [!NOTE] +> By default, the framework adapters only include the live devtools in development mode. In production builds they export no-op implementations unless you opt into the `/production` entrypoints. + +## Installation + +Install the TanStack Devtools host package and the Table adapter for your framework: + +### React + +```sh +npm install @tanstack/react-devtools @tanstack/react-table-devtools +``` + +### Preact + +```sh +npm install @tanstack/preact-devtools @tanstack/preact-table-devtools +``` + +### Solid + +```sh +npm install @tanstack/solid-devtools @tanstack/solid-table-devtools +``` + +### Vue + +```sh +npm install @tanstack/vue-devtools @tanstack/vue-table-devtools +``` + +Angular, Lit, Svelte, and vanilla do not currently ship dedicated table devtools adapters. + +## Setup Pattern + +The recommended setup has two parts: + +1. Mount `TanStackDevtools` at the app root with `tableDevtoolsPlugin()` +2. Call `useTanStackTableDevtools(table, name?)` immediately after creating each table + +If you register multiple tables, the Table panel shows a selector so you can switch between them. + +## React Setup + +```tsx +import React from 'react' +import ReactDOM from 'react-dom/client' +import { useTable } from '@tanstack/react-table' +import { TanStackDevtools } from '@tanstack/react-devtools' +import { + tableDevtoolsPlugin, + useTanStackTableDevtools, +} from '@tanstack/react-table-devtools' + +function App() { + const table = useTable({ + // ... + }) + + useTanStackTableDevtools(table, 'Users Table') + + return +} + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + , +) +``` + +See the [React row-selection example](./framework/react/examples/row-selection). + +## Preact Setup + +```tsx +import { render } from 'preact' +import { useTable } from '@tanstack/preact-table' +import { TanStackDevtools } from '@tanstack/preact-devtools' +import { + tableDevtoolsPlugin, + useTanStackTableDevtools, +} from '@tanstack/preact-table-devtools' + +function App() { + const table = useTable({ + // ... + }) + + useTanStackTableDevtools(table, 'Users Table') + + return +} + +render( + <> + + + , + document.getElementById('root')!, +) +``` + +See the [Preact sorting example](./framework/preact/examples/sorting). + +## Solid Setup + +```tsx +import { render } from 'solid-js/web' +import { createTable } from '@tanstack/solid-table' +import { TanStackDevtools } from '@tanstack/solid-devtools' +import { + tableDevtoolsPlugin, + useTanStackTableDevtools, +} from '@tanstack/solid-table-devtools' + +function App() { + const table = createTable({ + // ... + }) + + useTanStackTableDevtools(table, 'Users Table') + + return +} + +render( + () => ( + <> + + + + ), + document.getElementById('root')!, +) +``` + +See the [Solid row-selection example](./framework/solid/examples/row-selection). + +## Vue Setup + +```ts +// main.ts +import { createApp, defineComponent, h } from 'vue' +import { TanStackDevtools } from '@tanstack/vue-devtools' +import { tableDevtoolsPlugin } from '@tanstack/vue-table-devtools' +import App from './App.vue' + +const Root = defineComponent({ + setup() { + return () => [ + h(App), + h(TanStackDevtools, { + plugins: [tableDevtoolsPlugin()], + }), + ] + }, +}) + +createApp(Root).mount('#app') +``` + +```vue + +``` + +See the [Vue row-selection example](./framework/vue/examples/row-selection). + +## Naming Tables + +The optional second argument lets you label a table in the devtools selector: + +```ts +useTanStackTableDevtools(table, 'Orders Table') +``` + +If you omit the name, the devtools assign fallback names such as `Table 1` and `Table 2`. + +## Production Builds + +If you need the live devtools in production, import from the `/production` entrypoint for your framework package: + +```tsx +import { tableDevtoolsPlugin } from '@tanstack/react-table-devtools/production' +import { useTanStackTableDevtools } from '@tanstack/react-table-devtools/production' +``` + +Equivalent `/production` entrypoints are available for the Preact, Solid, and Vue adapters as well. diff --git a/docs/enterprise/ag-grid.md b/docs/enterprise/ag-grid.md deleted file mode 100644 index 6836b9af11..0000000000 --- a/docs/enterprise/ag-grid.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: AG Grid - An alternative enterprise data-grid solution ---- - -

- - - -

- -While we clearly love TanStack Table, we acknowledge that it is not a "batteries" included product packed with customer support and enterprise polish. We realize that some of our users may need this though! To help out here, we want to introduce you to AG Grid, an enterprise-grade data grid solution that can supercharge your applications with its extensive feature set and robust performance. While TanStack Table is also a powerful option for implementing data grids, we believe in providing our users with a diverse range of choices that best fit their specific requirements. AG Grid is one such choice, and we're excited to highlight its capabilities for you. - -## Why Choose [AG Grid](https://ag-grid.com/react-data-grid/?utm_source=reacttable&utm_campaign=githubreacttable)? - -Here are some good reasons to consider AG Grid for your next project: - -### Comprehensive Feature Set - -AG Grid offers an extensive set of features, making it a versatile and powerful data grid solution. With AG Grid, you get access to a wide range of functionalities that cater to the needs of complex enterprise applications. From advanced sorting, filtering, and grouping capabilities to column pinning, multi-level headers, and tree data structure support, AG Grid provides you with the tools to create dynamic and interactive data grids that meet your application's unique demands. - -### High Performance - -When it comes to handling large datasets and achieving exceptional performance, AG Grid delivers outstanding results. It employs highly optimized rendering techniques, efficient data updates, and virtualization to ensure smooth scrolling and fast response times, even when dealing with thousands or millions of rows of data. AG Grid's performance optimizations make it an excellent choice for applications that require high-speed data manipulation and visualization. - -### Customization and Extensibility - -AG Grid is designed to be highly customizable and extensible, allowing you to tailor the grid to your specific needs. It provides a rich set of APIs and events that enable you to integrate custom functionality seamlessly. You can define custom cell renderers, editors, filters, and aggregators to enhance the grid's behavior and appearance. AG Grid also supports a variety of themes, allowing you to match the grid's visual style to your application's design. - -### Support for Enterprise Needs - -As an enterprise-focused solution, AG Grid caters to the requirements of complex business applications. It offers enterprise-specific features such as row grouping, column pinning, server-side row model, master/detail grids, and rich editing capabilities. AG Grid also integrates well with other enterprise frameworks and libraries, making it a reliable choice for large-scale projects. - -### Active Development and Community Support - -AG Grid benefits from active development and a thriving community of developers. The team behind AG Grid consistently introduces new features and enhancements, ensuring that the product evolves to meet the changing needs of the industry. The community support is robust, with forums, documentation, and examples readily available to assist you in utilizing the full potential of AG Grid. - -## Conclusion - -While TanStack Table remains a powerful and flexible option for implementing data grids, we understand that different projects have different requirements. AG Grid offers a compelling enterprise-grade solution that may be particularly suited to your needs. Its comprehensive feature set, high performance, customization options, and focus on enterprise requirements make AG Grid an excellent choice for projects that demand a robust and scalable data grid solution. - -We encourage you to explore AG Grid further by visiting their website and trying out their demo. Remember that both TanStack Table and AG Grid have their unique strengths and considerations. We believe in providing options to our users, empowering you to make informed decisions and choose the best fit for your specific use case. - -Visit the [AG Grid website](https://www.ag-grid.com). diff --git a/docs/faq.md b/docs/faq.md index 32bda46315..714a9fa448 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -26,8 +26,8 @@ export default function MyComponent() { // ... ]; - //❌ Columns and data are defined in the same scope as `useReactTable` without a stable reference, will cause infinite loop! - const table = useReactTable({ + //❌ Columns and data are defined in the same scope as `useTable` without a stable reference, will cause infinite loop! + const table = useTable({ columns, data, }); @@ -64,7 +64,7 @@ export default function MyComponent() { ]); // Columns and data are defined in a stable reference, will not cause infinite loop! - const table = useReactTable({ + const table = useTable({ columns, data, }); @@ -89,7 +89,7 @@ export default function MyComponent() { //... }); - const table = useReactTable({ + const table = useTable({ columns, //❌ BAD: This will cause an infinite loop of re-renders because `data` is mutated in place (destroys stable reference) data: data?.filter(d => d.isActive) ?? [], @@ -118,7 +118,7 @@ export default function MyComponent() { //✅ GOOD: This will not cause an infinite loop of re-renders because `filteredData` is memoized const filteredData = useMemo(() => data?.filter(d => d.isActive) ?? [], [data]); - const table = useReactTable({ + const table = useTable({ columns, data: filteredData, // stable reference! }); @@ -156,7 +156,7 @@ React.useEffect(() => { skipPageResetRef.current = false }) -useReactTable({ +useTable({ ... autoResetPageIndex: !skipPageResetRef.current, autoResetExpanded: !skipPageResetRef.current, diff --git a/docs/framework/angular/angular-table.md b/docs/framework/angular/angular-table.md index c3e74071d2..e9553238f5 100644 --- a/docs/framework/angular/angular-table.md +++ b/docs/framework/angular/angular-table.md @@ -2,270 +2,171 @@ title: Angular Table --- -The `@tanstack/angular-table` adapter is a wrapper around the core table logic. Most of it's job is related to managing -state the "angular signals" way, providing types and the rendering implementation of cell/header/footer templates. +The `@tanstack/angular-table` adapter wraps `@tanstack/table-core` with Angular signals, rendering directives, dependency-injection helpers, and types. `injectTable` supplies Angular reactivity bindings for you, so table atoms can update Angular signals, computed values, effects, and templates. -## Exports +TanStack Table v9 is explicit about what a table uses. Register features with `_features`, and register client-side row model factories with `_rowModels`. The core row model is included by default, so a basic table can use `_rowModels: {}`. -`@tanstack/angular-table` re-exports all of `@tanstack/table-core`'s APIs and the following: +## Creating a Table -### `createAngularTable` - -Accepts an options function or a computed value that returns the table options, and returns a table. +Use `injectTable` inside an Angular injection context. The initializer is re-run when Angular signals read inside it change, then the adapter syncs the table options. ```ts -import {createAngularTable} from '@tanstack/angular-table' - -export class AppComponent { - data = signal([]) +import { ChangeDetectionStrategy, Component, signal } from '@angular/core' +import { + FlexRender, + injectTable, + tableFeatures, + type ColumnDef, +} from '@tanstack/angular-table' - table = createAngularTable(() => ({ - data: this.data(), - columns: defaultColumns, - getCoreRowModel: getCoreRowModel(), - })) +type Person = { + firstName: string + lastName: string + age: number } -// ...render your table in template - -``` - -### `FlexRender` - -An Angular structural directive for rendering cell/header/footer templates with dynamic values. - -FlexRender supports any type of content supported by Angular: - -- A string, or a html string via `innerHTML` -- A [TemplateRef](https://angular.dev/api/core/TemplateRef) -- A [Component](https://angular.dev/api/core/Component) wrapped into `FlexRenderComponent` +const _features = tableFeatures({}) -Example: +const columns: Array> = [ + { + accessorKey: 'firstName', + header: 'First name', + cell: (info) => info.getValue(), + }, +] -```ts @Component({ - imports: [FlexRenderDirective], - //... + selector: 'app-root', + imports: [FlexRender], + templateUrl: './app.html', + changeDetection: ChangeDetectionStrategy.OnPush, }) -``` - -```angular-html +export class App { + readonly data = signal([]) - -@for (row of table.getRowModel().rows; track row.id) { - - @for (cell of row.getVisibleCells(); track cell.id) { - - - - {{ cell }} - -
-
- -} - + readonly table = injectTable(() => ({ + _features, + _rowModels: {}, + columns, + data: this.data(), + })) } - ``` -#### Rendering a TemplateRef - -In order to render a TemplateRef into a specific column header/cell/footer, you can pass the TemplateRef into the column -definition. - -You can access the TemplateRef data via the `$implicit` property, which is valued based on what is passed in the props -field of flexRender. +For feature-specific row models, register the feature and put the row model factory under `_rowModels`. -In most cases, each TemplateRef will be rendered with the $implicit context valued based on the cell type in this way: +```ts +import { + createPaginatedRowModel, + createSortedRowModel, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/angular-table' -- Header: `HeaderContext` -- Cell: `CellContext`, -- Footer: `HeaderContext` +const _features = tableFeatures({ + rowPaginationFeature, + rowSortingFeature, +}) -```angular-html +const tableOptions = { + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, +} +``` - - - {{ cell }} - -
-
+## Table State - - - -``` +Table state is managed with TanStack Store atoms in v9. For most tables, you do not need to manage table state yourself: set `initialState` when you need starting values, and use feature APIs like `table.nextPage()`, `table.setSorting(...)`, and `row.toggleSelected()` instead of mutating state directly. -Full example: +Angular apps usually own state with signals and pass it through `state` with the matching `on[State]Change` option. The `atoms` table option is also available for TanStack Store atom ownership. Table atoms are backed by Angular signals, so reading `table.atoms.someSlice.get()` in a template, `computed(...)`, or `effect(...)` participates in Angular reactivity. -```angular-ts -import type { - CellContext, - ColumnDef, - HeaderContext, +```ts +import { signal } from '@angular/core' +import { + injectTable, + rowPaginationFeature, + tableFeatures, + type PaginationState, } from '@tanstack/angular-table' -import {Component, TemplateRef, viewChild} from '@angular/core' -@Component({ - template: ` - - @for (row of table.getRowModel().rows; track row.id) { - - @for (cell of row.getVisibleCells(); track cell.id) { - - - - {{ cell }} - -
-
- - } - - } - - - - {{ context.getValue() }} - - - {{ context.getValue() }} - - `, +const _features = tableFeatures({ + rowPaginationFeature, }) -class AppComponent { - customHeader = - viewChild.required }>>( - 'customHeader' - ) - customCell = - viewChild.required }>>( - 'customCell' - ) - - columns: ColumnDef[] = [ - { - id: 'customCell', - header: () => this.customHeader(), - cell: () => this.customCell(), + +export class App { + readonly pagination = signal({ + pageIndex: 0, + pageSize: 10, + }) + + readonly table = injectTable(() => ({ + _features, + _rowModels: {}, + columns, + data: this.data(), + state: { + pagination: this.pagination(), + }, + onPaginationChange: (updater) => { + this.pagination.update((old) => + updater instanceof Function ? updater(old) : updater, + ) }, - ] + })) } ``` -#### Rendering a Component +See the [Table State Guide](./guide/table-state.md) for reactive reads, external atoms, and state ownership patterns. -To render a Component into a specific column header/cell/footer, you can pass a `FlexRenderComponent instantiated with -your `ComponentType, with the ability to include optional parameters such as inputs and an injector. +## Rendering Headers, Cells, and Footers -```ts -import {FlexRenderComponent} from "@tanstack/angular-table"; - -class AppComponent { - columns: ColumnDef[] = [ - { - id: 'customCell', - header: () => new FlexRenderComponent( - CustomCellComponent, - {}, // optional inputs - injector // optional injector - ), - cell: () => this.customCell(), - }, - ] -} +Use the `FlexRender` directive helpers to render column `header`, `cell`, and `footer` definitions. They handle primitive values, templates, and components wrapped with `flexRenderComponent`. + +```html + + @for (row of table.getRowModel().rows; track row.id) { + + @for (cell of row.getVisibleCells(); track cell.id) { + + {{ rendered }} + + } + + } + ``` -Underneath, this utilizes -the [ViewContainerRef#createComponent](https://angular.dev/api/core/ViewContainerRef#createComponent) api. -Therefore, you should declare your custom inputs using the @Input decorator or input/model signals. +For `*flexRender`, `*flexRenderHeader`, `*flexRenderFooter`, `flexRenderComponent`, and render context helpers, see the [Rendering components Guide](./guide/rendering.md). -You can still access the table cell context through the `injectFlexRenderContext` function, which returns the context -value based on the props you pass to the `FlexRenderDirective`. +## createTableHook + +`createTableHook` creates app-specific Angular table helpers. Use it when multiple tables should share `_features`, `_rowModels`, default options, column helpers, and component conventions. ```ts -@Component({ - // ... +import { createTableHook, tableFeatures } from '@tanstack/angular-table' + +const { injectAppTable, createAppColumnHelper } = createTableHook({ + _features: tableFeatures({}), + _rowModels: {}, }) -class CustomCellComponent { - // context of a cell component - readonly context = injectFlexRenderContext>(); - // context of a header/footer component - readonly context = injectFlexRenderContext>(); -} -``` -Alternatively, you can render a component into a specific column header, cell, or footer by passing the component type -to the corresponding column definitions. These column definitions will be provided to the `flexRender` directive along with the `context`. +const columnHelper = createAppColumnHelper() -```ts -import {FlexRenderComponent} from "@tanstack/angular-table"; - -class AppComponent { - columns: ColumnDef[] = [ - { - id: 'select', - header: () => TableHeadSelectionComponent, - cell: () => TableRowSelectionComponent, - }, - ] +export class App { + readonly table = injectAppTable(() => ({ + columns, + data: this.data(), + })) } ``` -```angular2html - - {{ headerCell }} - -``` +See the [Table Composition Guide](./guide/table-composition.md) and the [Composable Tables example](./examples/composable-tables) for the full pattern. -Properties of `context` provided in the `flexRender` directive will be accessible to your component. -You can explicitly define the context properties required by your component. -In this example, the context provided to flexRender is of type HeaderContext. -Input signal `table`, which is a property of HeaderContext together with `column` and `header` properties, -is then defined to be used in the component. If any of the context properties are -needed in your component, feel free to use them. Please take note that only input signal is supported, -when defining access to context properties, using this approach. +## API Reference -```angular-ts -@Component({ - template: ` - - `, - // ... -}) -export class TableHeadSelectionComponent { - //column = input.required>() - //header = input.required>() - table = input.required>() -} -``` \ No newline at end of file +See the [Angular API Reference](./reference/index.md). diff --git a/docs/framework/angular/guide/migrating.md b/docs/framework/angular/guide/migrating.md new file mode 100644 index 0000000000..5cfc538939 --- /dev/null +++ b/docs/framework/angular/guide/migrating.md @@ -0,0 +1,759 @@ +--- +title: Migrating to TanStack Table v9 (Angular) +--- + +## What's New in TanStack Table v9 + +TanStack Table v9 is a major release that introduces significant architectural improvements while maintaining the core table logic you're familiar with. Here are the key changes: + +### 1. Tree-shaking + +- **Features are tree-shakeable**: Features are now treated as plugins—import only what you use. If your table only needs sorting, you won't ship filtering, pagination, or other feature code. Bundlers can eliminate unused code, so for smaller tables you can expect a meaningfully smaller bundle compared to v8. This also lets TanStack Table add features over time without bloating everyone's bundles. +- **Row models and their functions are refactored**: Row model factories (`createFilteredRowModel`, `createSortedRowModel`, etc.) now accept their processing functions (`filterFns`, `sortFns`, `aggregationFns`) as parameters. This enables tree-shaking of the functions themselves—if you use a custom filter, you don't pay for built-in filters you never use. + +### 2. State Management + +- **Uses TanStack Store**: The internal state system has been rebuilt on [TanStack Store](https://tanstack.com/store), providing a reactive, framework-agnostic foundation. +- **Opt-in subscriptions instead of memo hacks**: In Angular, table atoms are backed by signals. Use `computed(...)` when you want selector-style derivation or custom equality, and keep reads scoped to the state you actually need. + +### 3. Composability + +- **`tableOptions`**: New utilities let you compose and share table configurations. Define `_features`, `_rowModels`, and default options once, then reuse them across tables or pass them through `createTableHook`. +- **`createTableHook`** (optional, advanced): Create reusable, strongly typed Angular table factories with pre-bound features, row models, default options, and component registries. + +### The Good News: Most Upgrades Are Opt-in + +While v9 is a significant upgrade, **you don't have to adopt everything at once**: + +- **Don't want to think about tree-shaking yet?** You can start with `stockFeatures` to include most commonly used features. +- **Your table markup is largely unchanged.** How you render ``, ``, ``, ` + @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { + + @for (header of headerGroup.headers; track header.id) { + + } + + } + + + + @for (row of table.getRowModel().rows; track row.id) { + + @for (cell of row.getVisibleCells(); track cell.id) { + + } + + } + +``` + +## Cell rendering with custom props + +When you need full control over the `props` passed to the render function, use `*flexRender` directly. + +`FlexRenderDirective` accepts two inputs: + +- `flexRender` — the render definition (a column def function, a string, a `TemplateRef`, a component type, or a `flexRenderComponent(...)` wrapper) +- `flexRenderProps` — the props object passed to the render function and used as the implicit template context + +Standard usage: + +```html + + {{ rendered }} + +``` + +You can pass a custom props object to override the default context shape: + +```html + + {{ rendered }} + +``` + +Inside rendered components, the full props object is available via [`injectFlexRenderContext()`](#injectflexrendercontext). + +## Component rendering + +You can render Angular components from column definitions in two ways: + +### Using `flexRenderComponent` + +[`flexRenderComponent(component, options?)`](../reference/functions/flexRenderComponent) wraps a component type with explicit options for `inputs`, `outputs`, `injector`, `bindings`, and `directives`. + +Use this when you need to: + +- pass custom inputs not derived from the render context +- subscribe to component outputs +- provide a custom `Injector` +- use creation-time `bindings` (Angular v20+) +- apply host directives and binding values at runtime + +```ts +import { flexRenderComponent, type ColumnDef } from '@tanstack/angular-table' + +const columns: ColumnDef[] = [ + { + id: 'custom-cell', + cell: ctx => + flexRenderComponent(CustomCellComponent, { + inputs: { + content: ctx.row.original.firstName, + }, + outputs: { + clicked: value => { + console.log(value) + }, + }, + }), + }, +] +``` + +#### How inputs and outputs work + +**Inputs** are applied through [`ComponentRef.setInput(key, value)`](https://angular.dev/api/core/ComponentRef#setInput). This works with both `input()` signals and `@Input()` decorators. Inputs are diffed on every change detection cycle using `KeyValueDiffers` — only changed values trigger `setInput`. + +For object-like inputs, updates are reference-based: if the object reference is stable, Angular's default input equality semantics prevent unnecessary updates. + +**Outputs** work through `OutputEmitterRef` subscriptions. The factory reads the component instance property by name, checks that it is an `OutputEmitterRef`, and subscribes to it. When the output emits, the corresponding callback from `outputs` is invoked. Subscriptions are cleaned up automatically when the component is destroyed. + +#### `bindings` API (Angular v20+) + +`flexRenderComponent` also accepts `bindings` and `directives`, forwarded directly to [`ViewContainerRef.createComponent`](https://angular.dev/api/core/ViewContainerRef#createComponent) at creation time. + +This supports Angular programmatic rendering APIs for passing host directives and binding values at runtime. + +Unlike `inputs`/`outputs` (which are applied imperatively after creation), `bindings` are applied **at creation time** — they participate in the component's initial change detection cycle. + +```ts +import { + inputBinding, + outputBinding, + twoWayBinding, + signal, +} from '@angular/core' +import { flexRenderComponent } from '@tanstack/angular-table' + +readonly name = signal('Ada') + +cell: () => + flexRenderComponent(EditableNameCellComponent, { + bindings: [ + inputBinding('value', this.name), + outputBinding('valueChange', value => { + console.log('changed', value) + }), + twoWayBinding('value', this.name), + ], + }) +``` + +> Avoid mixing `bindings` with `inputs`/`outputs` on the same property. `bindings` are applied at creation, while `inputs`/`outputs` are applied post-creation — mixing them can lead to double initialization or conflicting values. + +See the Angular docs for details: + +- [Programmatic rendering — Binding inputs/outputs/directives](https://angular.dev/guide/components/programmatic-rendering#binding-inputs-outputs-and-setting-host-directives-at-creation) +- [`inputBinding`](https://angular.dev/api/core/inputBinding), [`outputBinding`](https://angular.dev/api/core/outputBinding), [`twoWayBinding`](https://angular.dev/api/core/twoWayBinding) + +### Returning a component class + +Return a component class from `header`, `cell`, or `footer`. + +The render context properties (`table`, `column`, `header`, `cell`, `row`, `getValue`, etc.) are automatically set as component inputs via `ComponentRef.setInput(...)`. + +Define input signals matching the context property names you need: + +```ts +import { Component, input } from '@angular/core' +import type { ColumnDef, Table, CellContext } from '@tanstack/angular-table' + +const columns: ColumnDef[] = [ + { + id: 'select', + header: () => TableHeadSelectionComponent, + cell: () => TableRowSelectionComponent, + }, +] + +@Component({ + template: ` + + `, +}) +export class TableHeadSelectionComponent { + readonly table = input.required>(); + // column = input.required>() + // header = input.required>() +} +``` + +Only properties declared with `input()` / `input.required()` are set — other context properties are silently ignored. You can also access the full context via [`injectFlexRenderContext()`](#injectflexrendercontext). + +## TemplateRef rendering + +You can return a `TemplateRef` from column definitions. The render context is passed as the template's `$implicit` context. + +Use `viewChild(...)` to capture template references: + +```ts +import { Component, TemplateRef, viewChild } from '@angular/core' +import type { + CellContext, + ColumnDef, + HeaderContext, +} from '@tanstack/angular-table' + +@Component({ + template: ` + + {{ context.column.id }} + + + + {{ context.getValue() }} + + `, +}) +export class AppComponent { + readonly customHeader = + viewChild.required }>>( + 'customHeader', + ) + readonly customCell = + viewChild.required }>>( + 'customCell', + ) + + readonly columns: ColumnDef[] = [ + { + id: 'templated', + header: () => this.customHeader(), + cell: () => this.customCell(), + }, + ] +} +``` + +`TemplateRef` rendering uses `createEmbeddedView` with an injector that includes the [DI context tokens](#dependency-injection). For reusable render blocks shared across multiple screens, prefer standalone components over `TemplateRef`. + +## Dependency injection + +`FlexRender` automatically provides DI tokens when rendering components and templates. These tokens are created in the `#getInjector` method of the renderer, which builds a child `Injector` with the render context properties. + +### `injectFlexRenderContext` + +[`injectFlexRenderContext()`](../reference/functions/injectFlexRenderContext) returns the full props object passed to `*flexRender`. The return type depends on the column definition slot: + +- In a `cell` definition: `CellContext` +- In a `header`/`footer` definition: `HeaderContext` + +```ts +import { Component } from '@angular/core' +import { + injectFlexRenderContext, + type CellContext, +} from '@tanstack/angular-table' + +@Component({ + template: ` + {{ context.getValue() }} + + `, +}) +export class InteractiveCellComponent { + readonly context = injectFlexRenderContext>() +} +``` + +Internally, the renderer wraps the context in a `Proxy` so that property access always reflects the latest values, even after re-renders. + +### Context directives + +Three optional directives let you expose table, header, and cell context to **any descendant** in the template — not just components rendered by `*flexRender`. + +This eliminates prop drilling: instead of passing data through multiple `input()` layers, any nested component or directive can inject the context directly. + +| Directive | Selector | Token | Inject helper | +|---|---|---|---| +| [`TanStackTable`](../reference/functions/injectTableContext) | `[tanStackTable]` | `TanStackTableToken` | `injectTableContext()` | +| [`TanStackTableHeader`](../reference/functions/injectTableHeaderContext) | `[tanStackTableHeader]` | `TanStackTableHeaderToken` | `injectTableHeaderContext()` | +| [`TanStackTableCell`](../reference/functions/injectTableCellContext) | `[tanStackTableCell]` | `TanStackTableCellToken` | `injectTableCellContext()` | + +Import them alongside `FlexRender`: + +```ts +import { + FlexRender, + TanStackTable, + TanStackTableHeader, + TanStackTableCell, +} from '@tanstack/angular-table' + +@Component({ + imports: [FlexRender, TanStackTable, TanStackTableHeader, TanStackTableCell], + templateUrl: './app.html', +}) +export class AppComponent {} +``` + +Apply them in the template to establish injection scopes: + +```html +
`, etc. remains the same. + +The main change is **how you define a table** with the Angular adapter — specifically the new `_features` and `_rowModels` options. + +--- + +## Quick Legacy Migration + +Angular does **not** ship a legacy API. + +If you're migrating an Angular project from TanStack Table v8 to v9, you will migrate directly to the v9 Angular adapter APIs (`injectTable`, `_features`, and `_rowModels`). + +--- + +The rest of this guide focuses on migrating to the full v9 API and taking advantage of its features. + +## Core Breaking Changes + +### Entrypoint Change + +The Angular adapter entrypoint to create a table instance is `injectTable`: + +```ts +// v8 +import { createAngularTable } from '@tanstack/angular-table' + +const v8Table = createAngularTable(() => ({ + // options +})) + +// v9 +import { injectTable } from '@tanstack/angular-table' + +const v9Table = injectTable(() => ({ + // options +})) +``` + +> Note: `injectTable` evaluates your initializer whenever any Angular signal read inside of it changes. +> Keep expensive/static values (like `columns`, `_features`, and `_rowModels`) as stable references outside the initializer. + +### New Required Options: `_features` and `_rowModels` + +In v9, you must explicitly declare which features and row models your table uses: + +```ts +// v8 +import { createAngularTable, getCoreRowModel } from '@tanstack/angular-table' + +const v8Table = createAngularTable(() => ({ + columns, + data: data(), + getCoreRowModel: getCoreRowModel(), +})) + +// v9 +import { + injectTable, + tableFeatures, +} from '@tanstack/angular-table' + +const _features = tableFeatures({}) // Empty = core feaFtures only + +// Define stable references outside the initializer +const v9Table = injectTable(() => ({ + _features, + _rowModels: {}, // Core row model is automatic + columns: this.columns, + data: this.data(), +})) +``` + +--- + +## The `_features` Option + +Features control what table functionality is available. In v8, all features were bundled. In v9, you import only what you need. + +### Importing Individual Features + +```ts +import { + tableFeatures, + // Import only the features you need + columnFilteringFeature, + rowSortingFeature, + rowPaginationFeature, + columnVisibilityFeature, + rowSelectionFeature, +} from '@tanstack/angular-table' + +// Create a features object (define this outside your injectTable initializer for stable reference) +const _features = tableFeatures({ + columnFilteringFeature, + rowSortingFeature, + rowPaginationFeature, + columnVisibilityFeature, + rowSelectionFeature, +}) +``` + +### Using `stockFeatures` for v8-like Behavior + +If you want all features without thinking about it (like v8), import `stockFeatures`: + +```ts +import { injectTable, stockFeatures } from '@tanstack/angular-table' + +class TableCmp { + readonly table = injectTable(() => ({ + _features: stockFeatures, // All features included + _rowModels: { /* ... */ }, + columns: this.columns, + data: this.data(), + })) +} +``` + +### Available Features + +| Feature | Import Name | +|---------|-------------| +| Column Filtering | `columnFilteringFeature` | +| Global Filtering | `globalFilteringFeature` | +| Row Sorting | `rowSortingFeature` | +| Row Pagination | `rowPaginationFeature` | +| Row Selection | `rowSelectionFeature` | +| Row Expanding | `rowExpandingFeature` | +| Row Pinning | `rowPinningFeature` | +| Column Pinning | `columnPinningFeature` | +| Column Visibility | `columnVisibilityFeature` | +| Column Ordering | `columnOrderingFeature` | +| Column Sizing | `columnSizingFeature` | +| Column Resizing | `columnResizingFeature` | +| Column Grouping | `columnGroupingFeature` | +| Column Faceting | `columnFacetingFeature` | + +--- + +## The `_rowModels` Option + +Row models are the functions that process your data (filtering, sorting, pagination, etc.). In v9, they're configured via `_rowModels` instead of `get*RowModel` options. + +### Migration Mapping + +| v8 Option | v9 `_rowModels` Key | v9 Factory Function | +|-----------|---------------------|---------------------| +| `getCoreRowModel()` | (automatic) | Not needed — always included | +| `getFilteredRowModel()` | `filteredRowModel` | `createFilteredRowModel(filterFns)` | +| `getSortedRowModel()` | `sortedRowModel` | `createSortedRowModel(sortFns)` | +| `getPaginationRowModel()` | `paginatedRowModel` | `createPaginatedRowModel()` | +| `getExpandedRowModel()` | `expandedRowModel` | `createExpandedRowModel()` | +| `getGroupedRowModel()` | `groupedRowModel` | `createGroupedRowModel(aggregationFns)` | +| `getFacetedRowModel()` | `facetedRowModel` | `createFacetedRowModel()` | +| `getFacetedMinMaxValues()` | `facetedMinMaxValues` | `createFacetedMinMaxValues()` | +| `getFacetedUniqueValues()` | `facetedUniqueValues` | `createFacetedUniqueValues()` | + +### Key Change: Row Model Functions Now Accept Parameters + +Several row model factories now accept their processing functions as parameters. This enables better tree-shaking and explicit configuration: + +```ts +import { + injectTable, + createFilteredRowModel, + createSortedRowModel, + createGroupedRowModel, + createPaginatedRowModel, + filterFns, // Built-in filter functions + sortFns, // Built-in sort functions + aggregationFns, // Built-in aggregation functions +} from '@tanstack/angular-table' + +class TableCmp { + readonly table = injectTable(() => ({ + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + sortedRowModel: createSortedRowModel(sortFns), + groupedRowModel: createGroupedRowModel(aggregationFns), + paginatedRowModel: createPaginatedRowModel(), + }, + columns: this.columns, + data: this.data(), + })) +} +``` + +### Full Migration Example + +```ts +// v8 +import { + injectTable, + getCoreRowModel, + getFilteredRowModel, + getSortedRowModel, + getPaginationRowModel, + filterFns, + sortingFns, +} from '@tanstack/angular-table' + +const v8Table = createAngularTable(() => ({ + columns, + data: data(), + getCoreRowModel: getCoreRowModel(), // used to be called "get*RowModel()" + getFilteredRowModel: getFilteredRowModel(), + getSortedRowModel: getSortedRowModel(), + getPaginationRowModel: getPaginationRowModel(), + filterFns, // used to be passed in as a root option + sortingFns, +})) + +// v9 +import { + injectTable, + tableFeatures, + columnFilteringFeature, + rowSortingFeature, + rowPaginationFeature, + createFilteredRowModel, + createSortedRowModel, + createPaginatedRowModel, + filterFns, + sortFns, +} from '@tanstack/angular-table' + +const _features = tableFeatures({ + columnFilteringFeature, + rowSortingFeature, + rowPaginationFeature, +}) + +const v9Table = injectTable(() => ({ + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data: data(), +})) +``` + +--- + +## State Management Changes + +### Accessing State + +In v8, you accessed state via `table.getState()`. In v9, state is accessed via the store: + +```ts +// v8 +const state = table.getState() +const v8 = table.getState() +const { sorting, pagination } = v8 + +// v9 - via the store +const fullState = table.store.state +const v9 = table.store.state +const { sorting: v9Sorting, pagination: v9Pagination } = v9 +``` + +### Optimizing Reads with Angular Signals + +In Angular, you have a few good options for consuming table state. + +#### Option 1: Read table atoms directly + +The Angular adapter backs table atoms with Angular signals. Read the atom you care about directly in templates, effects, or computed values. + +```ts +import { computed, effect } from '@angular/core' +import { shallow } from '@tanstack/angular-table' + +class TableCmp { + readonly table = injectTable(() => ({ + _features, + _rowModels: { /* ... */ }, + columns: this.columns, + data: this.data(), + })) + + // Use computed when deriving from a slice or applying equality. + private readonly pagination = computed( + () => this.table.atoms.pagination.get(), + { equal: shallow }, + ) + + constructor() { + effect(() => { + const { pageIndex, pageSize } = this.pagination() + console.log('Page', pageIndex, 'Size', pageSize) + }) + } +} +``` + +#### Option 2: Use `computed(...)` for selected object slices + +Use Angular `computed(...)` when you want selector-style behavior, a derived value, or an equality function. For object/array slices, use `shallow` from `@tanstack/angular-table` to avoid unnecessary downstream work when the slice is recreated with the same values. + +```ts +import { computed, effect } from '@angular/core' +import { shallow } from '@tanstack/angular-table' + +class TableCmp { + readonly table = injectTable(() => ({ + _features, + _rowModels: { /* ... */ }, + columns: this.columns, + data: this.data(), + })) + + // Provide an equality function for object slices + readonly pagination = computed( + () => this.table.store.state.pagination, + { equal: shallow }, + ) + + constructor() { + effect(() => { + // This effect only re-runs when pagination changes + const { pageIndex, pageSize } = this.pagination() + console.log('Page', pageIndex, 'Size', pageSize) + }) + } +} +``` + +### Controlled State + +Controlled state patterns are pretty the same as v8: + +```ts +import { signal } from '@angular/core' +import type { SortingState, PaginationState } from '@tanstack/angular-table' + +class TableCmp { + readonly sorting = signal([]) + readonly pagination = signal({ pageIndex: 0, pageSize: 10 }) + + readonly table = injectTable(() => ({ + _features, + _rowModels: { /* ... */ }, + columns: this.columns, + data: this.data(), + state: { + sorting: this.sorting(), + pagination: this.pagination(), + }, + onSortingChange: (updater) => { + updater instanceof Function + ? this.sorting.update(updater) + : this.sorting.set(updater) + }, + onPaginationChange: (updater) => { + updater instanceof Function + ? this.pagination.update(updater) + : this.pagination.set(updater) + }, + })) +} +``` + +--- + +## Column Helper Changes + +The `createColumnHelper` function now requires a `TFeatures` type parameter in addition to `TData`: + +```ts +// v8 +import { createColumnHelper } from '@tanstack/angular-table' + +const columnHelperV8 = createColumnHelper() + +// v9 +import { createColumnHelper, tableFeatures, rowSortingFeature } from '@tanstack/angular-table' + +const _features = tableFeatures({ rowSortingFeature }) +const columnHelperV9 = createColumnHelper() +``` + +### New `columns()` Helper Method + +v9 adds a `columns()` helper for better type inference when wrapping column arrays. + +```ts +const columnHelper = createColumnHelper() + +// Wrap your columns array for better type inference +const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('lastName', { + id: 'lastName', + header: () => 'Last Name', + cell: (info) => info.getValue(), + }), + columnHelper.display({ + id: 'actions', + header: 'Actions', + cell: () => 'Edit', + }), +]) +``` + +### Using with `createTableHook` + +When using `createTableHook`, you get a pre-bound `createAppColumnHelper` that only requires `TData`: + +```ts +import { createTableHook, tableFeatures, rowSortingFeature } from '@tanstack/angular-table' + +const { injectAppTable, createAppColumnHelper } = createTableHook({ + _features: tableFeatures({ rowSortingFeature }), + _rowModels: { /* ... */ }, +}) + +// TFeatures is already bound — only need TData! +const columnHelper = createAppColumnHelper() +``` + +--- + +## Rendering Changes + +### `FlexRender` + +The rendering primitives in the Angular adapter are `FlexRender` and the `*flexRender` directives. + +In v9, you can continue to render header/cell/footer content using the Angular adapter rendering utilities, but there are a few important improvements and helper APIs to be aware of. + +#### Structural directive rendering + +Angular rendering is directive-based: + +- `FlexRender` / `*flexRender` renders arbitrary render content (primitives, `TemplateRef`, component types, or `flexRenderComponent(...)` wrappers) +- The directive is responsible for mounting embedded views or components via `ViewContainerRef` + +#### Shorthand directives + +If you're rendering standard table content, prefer the shorthand helpers: + +- `*flexRenderCell="cell; let value"` +- `*flexRenderHeader="header; let value"` +- `*flexRenderFooter="footer; let value"` + +These automatically select the correct column definition (`columnDef.cell` / `header` / `footer`) and the right props (`cell.getContext()` / `header.getContext()`), so you don't need to manually provide `props:`. + +#### DI-aware render functions + context injection + +Column definition render functions (`header`, `cell`, `footer`) run inside an Angular injection context, so they can safely call `inject()` and use signals. + +When a component is rendered through the FlexRender directives, you can also access the full render props object via DI using `injectFlexRenderContext()`. + +#### Component rendering helper: `flexRenderComponent` + +If you need to render an Angular component with explicit configuration (custom `inputs`, `outputs`, `injector`, and Angular v20+ creation-time `bindings`/`directives`), return a `flexRenderComponent(Component, options)` wrapper from your column definition. + +For complete rendering details (including component rendering, `TemplateRef`, `flexRenderComponent`, and context helpers), see the [Rendering components Guide](./rendering.md). + +--- + +## The `tableOptions()` Utility + +The `tableOptions()` helper provides type-safe composition of table options. It's useful for creating reusable partial configurations that can be spread into your table setup. + +### Basic Usage + +```ts +import { injectTable, tableOptions, tableFeatures, rowSortingFeature } from '@tanstack/angular-table' +import { isDevMode } from '@angular/core'; + +// Create a reusable options object with features pre-configured +const baseOptions = tableOptions({ + _features: tableFeatures({ rowSortingFeature }), + debugTable: isDevMode() +}) + +class TableCmp { + readonly table = injectTable(() => ({ + ...baseOptions, + columns: this.columns, + data: this.data(), + _rowModels: {}, + })) +} +``` + +### Composing Partial Options + +`tableOptions()` allows you to omit certain required fields (like `data`, `columns`, or `_features`) when creating partial configurations: + +```ts +import { + tableOptions, + tableFeatures, + rowSortingFeature, + columnFilteringFeature, + createSortedRowModel, + createFilteredRowModel, + filterFns, + sortFns, +} from '@tanstack/angular-table' + +// Partial options without data or columns +const featureOptions = tableOptions({ + _features: tableFeatures({ + rowSortingFeature, + columnFilteringFeature, + }), + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + filteredRowModel: createFilteredRowModel(filterFns), + }, +}) +``` + +```ts +import { injectTable, tableOptions, createPaginatedRowModel } from '@tanstack/angular-table' + +// Another partial without _features (inherits from spread) +const paginationDefaults = tableOptions({ + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + }, + initialState: { + pagination: { pageIndex: 0, pageSize: 25 }, + }, +}) + +class TableCmp { + readonly table = injectTable(() => ({ + ...featureOptions, + ...paginationDefaults, + columns: this.columns, + data: this.data(), + })) +} +``` + +### Using with `createTableHook` + +`tableOptions()` pairs well with `createTableHook` for building composable table factories: + +```ts +import { + createTableHook, + tableOptions, + tableFeatures, + rowSortingFeature, + rowPaginationFeature, + createSortedRowModel, + createPaginatedRowModel, + sortFns, +} from '@tanstack/angular-table' + +const sharedOptions = tableOptions({ + _features: tableFeatures({ rowSortingFeature, rowPaginationFeature }), + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, +}) + +const { injectAppTable } = createTableHook(sharedOptions) +``` + +--- + +## `createTableHook`: Composable Table Patterns + +**This is an advanced, optional feature.** You don't need to use `createTableHook`—`injectTable` is sufficient for most use cases. + +For applications with multiple tables sharing the same configuration, `createTableHook` lets you define features, row models, and reusable components once. + +For full setup and patterns, see the [Table composition Guide](./table-composition.md). + +--- + +## Other Breaking Changes + +### Column Pinning Option Split + +The `enablePinning` option has been split into separate options: + +```ts +// v8 +enablePinning: true + +// v9 +enableColumnPinning: true +enableRowPinning: true +``` + +### Removed Internal APIs + +All internal APIs prefixed with `_` have been removed. If you were using any of these, use their public equivalents. + +### Column Sizing vs. Column Resizing Split + +In v8, column sizing and resizing were combined in a single feature. In v9, they've been split into separate features for better tree-shaking. + +| v8 | v9 | +|----|-----| +| `ColumnSizing` (combined feature) | `columnSizingFeature` + `columnResizingFeature` | +| `columnSizingInfo` state | `columnResizing` state | +| `setColumnSizingInfo()` | `setColumnResizing()` | +| `onColumnSizingInfoChange` option | `onColumnResizingChange` option | + +If you only need column sizing (fixed widths) without interactive resizing, you can import just `columnSizingFeature`. If you need drag-to-resize functionality, import both. + +### Sorting API Renames + +Sorting-related APIs have been renamed for consistency: + +| v8 | v9 | +|----|-----| +| `sortingFn` (column def option) | `sortFn` | +| `column.getSortingFn()` | `column.getSortFn()` | +| `column.getAutoSortingFn()` | `column.getAutoSortFn()` | +| `SortingFn` type | `SortFn` type | +| `SortingFns` interface | `SortFns` interface | +| `sortingFns` (built-in functions) | `sortFns` | + +Update your column definitions. + +### Row API Changes + +Some row APIs have changed from private to public: + +| v8 | v9 | +|----|-----| +| `row._getAllCellsByColumnId()` (private) | `row.getAllCellsByColumnId()` (public) | + +--- + +## TypeScript Changes Summary + +### Type Generics + +Most types now require a `TFeatures` parameter: + +```txt +// v8 +type Column +type ColumnDef +type Table +type Row +type Cell + +// v9 +type Column +type ColumnDef +type Table +type Row +type Cell +``` + +### Using `typeof _features` + +The easiest way to get the `TFeatures` type is with `typeof`: + +```ts +const _features = tableFeatures({ + rowSortingFeature, + columnFilteringFeature, +}) + +type MyFeatures = typeof _features + +const columns: ColumnDef[] = [...] +``` + +### Using `StockFeatures` + +If using `stockFeatures`, use the `StockFeatures` type: + +```ts +import type { StockFeatures, ColumnDef } from '@tanstack/angular-table' + +const columns: ColumnDef[] = [...] +``` + +### `ColumnMeta` Generic Change + +If you're using module augmentation to extend `ColumnMeta`, note that it now requires a `TFeatures` parameter. + +### `RowData` Type Restriction + +The `RowData` type is now more restrictive. + +--- + +## Migration Checklist + +- [ ] Update your table setup to v9 and define `_features` using `tableFeatures()` (or use `stockFeatures`) +- [ ] Migrate `get*RowModel()` options to `_rowModels` +- [ ] Update row model factories to include `Fns` parameters where needed +- [ ] Update TypeScript types to include `TFeatures` generic +- [ ] Update state access: `table.getState()` → `table.store.state` +- [ ] Update `createColumnHelper()` → `createColumnHelper()` +- [ ] Replace `enablePinning` with `enableColumnPinning`/`enableRowPinning` if used +- [ ] Rename `sortingFn` → `sortFn` in column definitions +- [ ] Split column sizing/resizing: use both `columnSizingFeature` and `columnResizingFeature` if needed +- [ ] Rename `columnSizingInfo` state → `columnResizing` (and related options) +- [ ] Update `ColumnMeta` module augmentation to include `TFeatures` generic (if used) +- [ ] (Optional) Use `tableOptions()` for composable configurations +- [ ] (Optional) Use `createTableHook` for reusable table patterns + +--- + +## Examples + +Check out these examples to see v9 patterns in action: +- [Basic](../examples/basic) +- [Basic (App Table)](../examples/basic-app-table) +- [Filters](../examples/filters) +- [Column Ordering](../examples/column-ordering) +- [Column Pinning](../examples/column-pinning) +- [Column Visibility](../examples/column-visibility) +- [Expanding](../examples/expanding) +- [Grouping](../examples/grouping) +- [Row Selection](../examples/row-selection) +- [Composable Tables](../examples/composable-tables) diff --git a/docs/framework/angular/guide/rendering.md b/docs/framework/angular/guide/rendering.md new file mode 100644 index 0000000000..d4f5e9190b --- /dev/null +++ b/docs/framework/angular/guide/rendering.md @@ -0,0 +1,421 @@ +--- +title: Rendering components +--- + +The `@tanstack/angular-table` adapter provides structural directives and dependency injection primitives for rendering table content in Angular templates. + +## FlexRender + +[`FlexRender`](../reference/variables/FlexRender) is the rendering primitive. +It is exported as a tuple of two directives: + +- [`FlexRenderDirective`](../reference/classes/FlexRenderDirective) — the base structural directive (`*flexRender`) +- [`FlexRenderCell`](../reference/classes/FlexRenderCell.md) — shorthand directives (`*flexRenderCell`, `*flexRenderHeader`, `*flexRenderFooter`) + +Import `FlexRender` to get both: + +```ts +import { Component } from '@angular/core' +import { FlexRender } from '@tanstack/angular-table' + +@Component({ + imports: [FlexRender], + templateUrl: './app.html', +}) +export class AppComponent {} +``` + +### How it works + +`FlexRender` is an Angular **structural directive**. Internally, it resolves the column definition's `header`, `cell`, or `footer` function and renders the result using [`ViewContainerRef`](https://angular.dev/api/core/ViewContainerRef): + +- **Primitives** (`string`, `number`): rendered via `createEmbeddedView` into the host `ng-template`. The value is exposed as the template's implicit context (`let value`). +- **`TemplateRef`**: rendered via `createEmbeddedView`. The render context (`CellContext`, `HeaderContext`) is passed as `$implicit`. +- **`flexRenderComponent(...)`**: rendered via `createComponent` with explicit `inputs`, `outputs`, `bindings`, `directives`, and `injector`. +- **Component type** (`Type`): rendered via [`createComponent`](https://angular.dev/api/core/ViewContainerRef#createComponent). All properties from the render context are set as component inputs through [`ComponentRef.setInput`](https://angular.dev/api/core/ComponentRef#setInput). + +Column definition functions (`header`, `cell`, `footer`) are called inside [`runInInjectionContext`](https://angular.dev/api/core/runInInjectionContext), which means you can call `inject()`, use signals, and access DI tokens directly in your render logic. + +## Cell rendering + +Prefer the shorthand directives for standard rendering: + +| Directive | Input | Column definition | +|---|---|---| +| `*flexRenderCell` | `Cell` | `columnDef.cell` | +| `*flexRenderHeader` | `Header` | `columnDef.header` | +| `*flexRenderFooter` | `Header` | `columnDef.footer` | + +Each shorthand resolves the correct column definition function and render context automatically through a `computed` signal, so no manual `props` mapping is needed. + +### Example + +```html +
+ @if (!header.isPlaceholder) { + + {{ value }} + + } +
+ + {{ value }} + +
+ + + @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { + + @for (header of headerGroup.headers; track header.id) { + + } + + } + + @for (row of table.getRowModel().rows; track row.id) { + + @for (cell of row.getVisibleCells(); track cell.id) { + + } + + } +
+ +
+ +
+``` + +Any component nested inside a `[tanStackTableCell]` host can inject the cell context: + +```ts +import { Component } from '@angular/core' +import { injectTableCellContext } from '@tanstack/angular-table' + +@Component({ + template: ` + + `, +}) +export class CellActionsComponent { + readonly cell = injectTableCellContext() + + onAction() { + console.log('Cell:', this.cell()) + } +} +``` + +```html + + + + +``` + +Each directive uses Angular's `providers` array to register a factory that reads its own input signal. + +This means the token is scoped to the directive's host element and its descendants. Multiple `[tanStackTableCell]` directives on different elements provide independent contexts. + +### Automatic token injection in FlexRender + +When `FlexRender` renders a component or template, it also provides DI tokens automatically based on the render context shape. In the renderer's `#getInjector` method, if the context object contains `table`, `cell`, or `header` properties, the corresponding `TanStackTableToken`, `TanStackTableCellToken`, or `TanStackTableHeaderToken` tokens are provided in the child injector. + +This means that even **without** the context directives, components rendered via `*flexRender` can use `injectTableContext()`, `injectTableCellContext()`, and `injectTableHeaderContext()`. The context directives are only needed for components that live **outside** the `*flexRender` rendering tree (e.g. sibling components in the same ``). diff --git a/docs/framework/angular/guide/table-composition.md b/docs/framework/angular/guide/table-composition.md new file mode 100644 index 0000000000..f56e9a8520 --- /dev/null +++ b/docs/framework/angular/guide/table-composition.md @@ -0,0 +1,295 @@ +--- +title: Table Composition (createTableHook) +--- + +`createTableHook` is a convenience API for creating reusable, type-safe table configurations with pre-bound components. It is inspired by [TanStack Form's `createFormHook`](https://tanstack.com/form/latest/docs/framework/react/guides/form-composition) — a pattern where you define shared infrastructure once and consume it across your application with minimal boilerplate. + +> **When to use it:** Use `createTableHook` when you have multiple tables that share the same configuration (features, row models, and reusable components). For a single table, `injectTable` is sufficient. + +## Examples + +- [Composable Tables](../examples/composable-tables) — Two tables (Users and Products) sharing the same `createTableHook` configuration, with table/cell/header components, sorting, filtering, and pagination. +- [Basic App Table](../examples/basic-app-table) — Minimal example using `createTableHook` with no pre-bound components. + +### createTableHook + +`createTableHook` centralizes your table configuration into a single factory call. It returns a set of typed functions — `injectAppTable`, `createAppColumnHelper`, and pre-typed injection helpers — that you use instead of the base APIs. + +## Setup + +Call `createTableHook` with your shared configuration and destructure the returned utilities: + +```ts +// table.ts — shared table infrastructure + +import { + createTableHook, + tableFeatures, + columnFilteringFeature, + createFilteredRowModel, + createPaginatedRowModel, + createSortedRowModel, + filterFns, + rowPaginationFeature, + rowSortingFeature, + sortFns, +} from '@tanstack/angular-table' + +import { PaginationControls, RowCount, TableToolbar } from './components/table-components' +import { TextCell, NumberCell, StatusCell, ProgressCell } from './components/cell-components' +import { SortIndicator, ColumnFilter } from './components/header-components' + +export const { + createAppColumnHelper, + injectAppTable, + injectTableContext, + injectTableCellContext, + injectTableHeaderContext, +} = createTableHook({ + // Features and row models are shared across all tables + _features: tableFeatures({ + columnFilteringFeature, + rowPaginationFeature, + rowSortingFeature, + }), + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + }, + // Default table options applied to every table + getRowId: (row) => row.id, + + // Pre-bound component registries + tableComponents: { + PaginationControls, + RowCount, + TableToolbar, + }, + cellComponents: { + TextCell, + NumberCell, + StatusCell, + ProgressCell, + }, + headerComponents: { + SortIndicator, + ColumnFilter, + }, +}) +``` + +This single file becomes the source of truth for your application's table infrastructure. + +## What `createTableHook` returns + +| Export | Description | +|---|---| +| `injectAppTable` | A wrapper around `injectTable` that merges default options and attaches component registries. Returns an `AppAngularTable` with table/cell/header components available directly on the instance. | +| `createAppColumnHelper` | A typed column helper where `cell`, `header`, and `footer` definitions receive enhanced context types with the registered components. | +| `injectTableContext` | Pre-typed `injectTableContext()` bound to your feature set. | +| `injectTableCellContext` | Pre-typed `injectTableCellContext()` bound to your feature set. | +| `injectTableHeaderContext` | Pre-typed `injectTableHeaderContext()` bound to your feature set. | +| `injectFlexRenderCellContext` | Pre-typed `injectFlexRenderContext()` for cell context. | +| `injectFlexRenderHeaderContext` | Pre-typed `injectFlexRenderContext()` for header context. | + +## Component registries + +`createTableHook` accepts three component registries that map string keys to Angular components or render functions: + +### `tableComponents` + +Components that need access to the **table instance**. These are attached directly to the `AppAngularTable` object returned by `injectAppTable`, so you can reference them in templates as `table.PaginationControls`, `table.RowCount`, etc. + +Use `injectTableContext()` inside these components to access the table: + +```ts +@Component({ + selector: 'app-pagination-controls', + template: ` + + `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class PaginationControls { + readonly table = injectTableContext() +} +``` + +Render table components via Angular `NgComponentOutlet`: + +```html + + + +``` + +### `cellComponents` + +Components that render **cell content**. These are attached to the `Cell` prototype, so they are available in column definitions through the enhanced `AppCellContext`: + +```ts +const columnHelper = createAppColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + cell: ({ cell }) => cell.TextCell, + }), + columnHelper.accessor('age', { + header: 'Age', + cell: ({ cell }) => cell.NumberCell, + }), + columnHelper.accessor('status', { + header: 'Status', + cell: ({ cell }) => cell.StatusCell, + }), +]) +``` + +Use `injectTableCellContext()` or `injectFlexRenderContext()` inside cell components: + +```ts +@Component({ + selector: 'span', + template: `{{ cell().getValue() }}`, +}) +export class TextCell { + readonly cell = injectTableCellContext() +} +``` + +### `headerComponents` + +Components or render functions that render **header/footer content**. These are attached to the `Header` prototype and available through the enhanced `AppHeaderContext`: + +```ts +// Render functions work too — they run in injection context +export function SortIndicator(): string | null { + const header = injectTableHeaderContext() + const sorted = header().column.getIsSorted() + if (!sorted) return null + return sorted === 'asc' ? '🔼' : '🔽' +} +``` + +Access header components in the template via `table.appHeader(header)`: + +```html +@for (_header of headerGroup.headers; track _header.id) { + @let header = table.appHeader(_header); + + {{ value }} + +
+
+ +} +``` + +## Using `injectAppTable` + +`injectAppTable` is a wrapper around `injectTable`. It merges the default options from `createTableHook` with the per-table options, and returns an `AppAngularTable` — the standard table instance augmented with: + +- **Table components** directly on the table object (`table.PaginationControls`, `table.TableToolbar`, etc.) +- **`table.appCell(cell)`** — utility type functions for templates that wraps a `Cell` with the registered `cellComponents` +- **`table.appHeader(header)`** — utility type functions for templates that wraps a `Header` with the registered `headerComponents` +- **`table.appFooter(footer)`** — utility type functions for templates that wraps a `Header` (footer) with the registered `headerComponents` + +You do not need to pass `_features` or `_rowModels` — they are inherited from the hook configuration: + +```ts +@Component({ + selector: 'users-table', + templateUrl: './users-table.html', + imports: [FlexRender, TanStackTable, TanStackTableHeader, TanStackTableCell, NgComponentOutlet], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class UsersTable { + readonly data = signal(makeData(100)) + + readonly columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + cell: ({ cell }) => cell.TextCell, + }), + // ... + ]) + + // No need to specify _features, _rowModels, ... — they come from createTableHook + table = injectAppTable(() => ({ + columns: this.columns, + data: this.data(), + })) +} +``` + +## Using `createAppColumnHelper` + +`createAppColumnHelper()` returns a column helper identical to `createColumnHelper` at runtime, but with enhanced types: the `cell`, `header`, and `footer` definition callbacks receive `AppCellContext` / `AppHeaderContext` instead of the base context types. + +This means TypeScript knows about your registered components and provides autocompletion: + +```ts +const columnHelper = createAppColumnHelper() + +columnHelper.accessor('firstName', { + cell: ({ cell }) => { + // ✅ TypeScript knows about TextCell, NumberCell, StatusCell, etc. + return cell.TextCell + }, + header: ({ header }) => { + // ✅ TypeScript knows about SortIndicator, ColumnFilter, etc. + return flexRenderComponent(header.SortIndicator) + }, +}) +``` + +You can also use `flexRenderComponent(...)` to wrap the component with custom inputs/outputs: + +```ts +columnHelper.accessor('firstName', { + cell: ({ cell }) => flexRenderComponent(cell.TextCell), + footer: ({ header }) => flexRenderComponent(header.FooterColumnId), +}) +``` + +## Multiple table configurations + +You can call `createTableHook` multiple times to create different table configurations for different parts of your application. Each call returns an independent set of utilities with its own feature set and component registries: + +```ts +// admin-table.ts — tables with editing capabilities +export const { + injectAppTable: injectAdminTable, + createAppColumnHelper: createAdminColumnHelper, +} = createTableHook({ + _features: tableFeatures({ rowSortingFeature, columnFilteringFeature }), + _rowModels: { /* ... */ }, + cellComponents: { EditableCell, DeleteButton }, +}) + +// readonly-table.ts — simpler read-only tables +export const { + injectAppTable: injectReadonlyTable, + createAppColumnHelper: createReadonlyColumnHelper, +} = createTableHook({ + _features: tableFeatures({ rowSortingFeature }), + _rowModels: { /* ... */ }, + cellComponents: { TextCell, NumberCell }, +}) +``` + +## Examples + +- [Composable Tables](../examples/composable-tables) — Full example with two tables sharing the same `createTableHook` configuration. +- [Basic App Table](../examples/basic-app-table) — Minimal example with no pre-bound components. diff --git a/docs/framework/angular/guide/table-state.md b/docs/framework/angular/guide/table-state.md index 40c732299f..1f78ebffa9 100644 --- a/docs/framework/angular/guide/table-state.md +++ b/docs/framework/angular/guide/table-state.md @@ -2,216 +2,285 @@ title: Table State (Angular) Guide --- +## Examples + +Want to skip to the implementation? Check out these examples: + +- [Basic injectTable](../examples/basic-inject-table) + ## Table State (Angular) Guide -TanStack Table has a simple underlying internal state management system to store and manage the state of the table. It also lets you selectively pull out any state that you need to manage in your own state management. This guide will walk you through the different ways in which you can interact with and manage the state of the table. +> **If you boil TanStack Table down to one sentence: TanStack Table is a large state-management coordinator for table states.** + +Understanding this guide is fundamental to understanding how TanStack Table works and how to interact with it for the best results. + +### Do you need to Manage External State? + +You usually do NOT need to manage table state yourself. If you pass nothing to `initialState`, `atoms`, `state`, or any of the `on[State]Change` table options, TanStack Table will manage its own state internally. + +There will be situations where you need to customize how you interact with the internal table state, or even hoist it up to your own scopes. TanStack Table lets you read, subscribe to, or own the state slices that matter to your app. This guide explains how table state works in Angular, how to read it, and when to use Angular signals or external state. + +### State in v9 + +TanStack Table v9 overhauled state management around TanStack Store. TanStack Store uses the `alien-signals` implementation and supports performant derived state. For Angular, the table adapter supplies reactivity bindings so table state atoms are backed by Angular signals. + +A table instance has a few state surfaces: + +- `table.baseAtoms` are the internal writable atoms created from the resolved initial state. +- `table.atoms` are readonly derived atoms exposed per registered state slice. +- `table.store` is a readonly flat TanStack Store derived by putting all of the registered `table.atoms` together. + +The Angular adapter provides `angularReactivity(injector)` as the table's reactivity binding. Core readonly atoms are Angular `computed` values, writable atoms are Angular `signal` values, and subscriptions bridge through `toObservable(computed(...), { injector })`. `injectTable` reruns the options initializer when Angular signals read inside it change, then calls `table.setOptions`. + +The returned table is also signal-reactive: table state and table APIs are wired for Angular signals, so you can consume table methods inside `computed(...)` and `effect(...)` and have those computations update when the underlying atom reads change. + +### Feature-based State + +State slices are only created for the features that are registered in `_features`. This keeps TanStack Table tree-shakeable and gives TypeScript more accurate state inference. + +```ts +const _features = tableFeatures({ + rowPaginationFeature, + rowSortingFeature, +}) + +readonly table = injectTable(() => ({ + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, + columns, + data: this.data(), +})) + +this.table.atoms.pagination.get() +this.table.atoms.sorting.get() + +// this.table.atoms.rowSelection // TypeScript error unless rowSelectionFeature is registered +``` + +If `_features` does not include a feature, its state should not be available in `table.atoms`, `table.store.state`, `initialState`, `state`, or `atoms`. ### Accessing Table State -You do not need to set up anything special in order for the table state to work. If you pass nothing into either `state`, `initialState`, or any of the `on[State]Change` table options, the table will manage its own state internally. You can access any part of this internal state by using the `table.getState()` table instance API. +There are two different questions when reading table state: + +- Do you only need the current value? +- Or should an Angular signal, computed value, effect, or template update when that value changes? + +Use a direct atom read for the current value. Because Angular table atoms are backed by Angular signals, the same read also participates in Angular dependency tracking when it happens inside a template, `computed(...)`, or `effect(...)`. + +#### Reading State + +The simplest and most performant way to read a state value is to read the matching atom: + +```ts +const pagination = this.table.atoms.pagination.get() +const sorting = this.table.atoms.sorting.get() +``` + +You can also read the current flat store snapshot: + +```ts +const tableState = this.table.store.state +const pagination = this.table.store.state.pagination +``` + +Atom reads are signal reads in Angular. If `this.table.atoms.pagination.get()` is used in a template expression, `computed(...)`, or `effect(...)`, Angular tracks it and updates when that atom changes. + +#### Selecting State with Angular computed + +Use Angular's native `computed(...)` when you want to derive a value from table state or apply a custom equality function. For object or array slices, pass `shallow` to avoid unnecessary downstream work when the selected value is structurally unchanged. ```ts -table = createAngularTable(() => ({ - columns: this.columns, +import { computed } from '@angular/core' +import { shallow } from '@tanstack/angular-table' + +readonly table = injectTable(() => ({ + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + }, + columns, data: this.data(), - //... })) -someHandler() { - console.log(this.table.getState()) //access the entire internal state - console.log(this.table.getState().rowSelection) //access just the row selection state -} +readonly pagination = computed( + () => this.table.atoms.pagination.get(), + // if you want to pass a custom equality function + // { equal: shallow }, +) + +readonly pageIndex = computed(() => this.pagination().pageIndex) +``` + +You can also select from the flat store if that is more convenient. + +```ts +readonly pagination = computed( + () => this.table.store.state.pagination, + { equal: shallow }, +) +``` + +Use `computed(...)` for selection, derivation, and equality control. You do not need it just to make an atom reactive; the atom already is backed by an Angular signal. + +### Setting Table State + +You should almost never need to set table state directly. TanStack Table features expose dedicated APIs for interacting with their state, and those APIs are the safest way to make changes. + +```ts +this.table.nextPage() +this.table.previousPage() +this.table.setPageIndex(0) +this.table.setPageSize(25) +``` + +Use APIs like `table.setSorting(...)`, `table.setColumnFilters(...)`, `column.toggleVisibility()`, or `row.toggleSelected()` instead of manually editing the underlying state object. + +If you only care about setting starting values, use `initialState`. If you want to reset a state slice back to its initial value, use that feature's reset API. + +If you really do need to write a state slice directly, the low-level write surface for internally owned state is the matching base atom: + +```ts +this.table.baseAtoms.pagination.set((old) => ({ + ...old, + pageIndex: 0, +})) ``` +Direct base atom writes should be rare. If a slice is owned by an external atom passed through `atoms`, write to that external atom instead; `table.atoms.pagination` will read from the external atom, not the internal base atom. + ### Custom Initial State -If all you need to do for certain states is customize their initial default values, you still do not need to manage any of the state yourself. You can simply set values in the `initialState` option of the table instance. +If you only need to customize the starting value for some table state, use `initialState`. You still do not need to manage that state yourself. -```jsx -table = createAngularTable(() => ({ - columns: this.columns, +`initialState` only applies to registered state slices. It is used to create the table's initial state and is also used by reset APIs such as `table.resetSorting()` or `table.resetPagination()`. Changing the `initialState` object later does not reset table state. + +```ts +readonly table = injectTable(() => ({ + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, + columns, data: this.data(), initialState: { - columnOrder: ['age', 'firstName', 'lastName'], //customize the initial column order - columnVisibility: { - id: false //hide the id column by default - }, - expanded: true, //expand all rows by default sorting: [ { id: 'age', - desc: true //sort by age in descending order by default - } - ] + desc: true, + }, + ], + pagination: { + pageIndex: 0, + pageSize: 25, + }, }, - //... })) ``` -> **Note**: Only specify each particular state in either `initialState` or `state`, but not both. If you pass in a particular state value to both `initialState` and `state`, the initialized state in `state` will take overwrite any corresponding value in `initialState`. +> **Note:** Do not provide the same state slice in multiple ownership places unless you intentionally want one to win. For a slice like `pagination`, prefer exactly one of `initialState.pagination`, `atoms.pagination`, or `state.pagination` as the source of truth. External atoms take precedence over external `state`; external `state` syncs into the table's internal base atom. -### Controlled State +#### Resetting to Initial State -If you need easy access to the table state in other areas of your application, TanStack Table makes it easy to control and manage any or all of the table state in your own state management system. You can do this by passing in your own state and state management functions to the `state` and `on[State]Change` table options. +Feature reset APIs reset to `table.initialState` by default. Many reset APIs also accept `true` to reset to that feature's blank/default state instead: -#### Individual Controlled State +```ts +this.table.resetSorting() +this.table.resetPagination() +this.table.resetPagination(true) +``` -You can control just the state that you need easy access to. You do NOT have to control all of the table state if you do not need to. It is recommended to only control the state that you need on a case-by-case basis. +Slice reset APIs like `resetPagination()` update through that feature's state updater and can update externally owned state. The core `table.reset()` API resets the internal base atoms, so do not use it as the primary way to reset state that is owned outside the table. -In order to control a particular state, you need to both pass in the corresponding `state` value and the `on[State]Change` function to the table instance. +### Controlled State -Let's take filtering, sorting, and pagination as an example in a "manual" server-side data fetching scenario. You can store the filtering, sorting, and pagination state in your own state management, but leave out any other state like column order, column visibility, etc. if your API does not care about those values. +If you need easy access to table state in other parts of your application, you can control individual state slices. In Angular, the common pattern is to own those values with Angular signals and pass them through `state` plus the matching `on[State]Change` callback. -```ts -import {signal} from '@angular/core'; -import {SortingState, ColumnFiltersState, PaginationState} from '@tanstack/angular-table' -import {toObservable} from "@angular/core/rxjs-interop"; -import {combineLatest, switchMap} from 'rxjs'; - -class TableComponent { - readonly columnFilters = signal([]) //no default filters - readonly sorting = signal([ - { - id: 'age', - desc: true, //sort by age in descending order by default - } - ]) - readonly pagination = signal({ - pageIndex: 0, - pageSize: 15 - }) - - //Use our controlled state values to fetch data - readonly data$ = combineLatest({ - filters: toObservable(this.columnFilters), - sorting: toObservable(this.sorting), - pagination: toObservable(this.pagination) - }).pipe( - switchMap(({filters, sorting, pagination}) => fetchData(filters, sorting, pagination)) - ) - readonly data = toSignal(this.data$); - - readonly table = createAngularTable(() => ({ - columns: this.columns, - data: this.data(), - //... - state: { - columnFilters: this.columnFilters(), //pass controlled state back to the table (overrides internal state) - sorting: this.sorting(), - pagination: this.pagination(), - }, - onColumnFiltersChange: updater => { //hoist columnFilters state into our own state management - updater instanceof Function - ? this.columnFilters.update(updater) - : this.columnFilters.set(updater) - }, - onSortingChange: updater => { - updater instanceof Function - ? this.sorting.update(updater) - : this.sorting.set(updater) - }, - onPaginationChange: updater => { - updater instanceof Function - ? this.pagination.update(updater) - : this.pagination.set(updater) - }, - })) -} +#### External Atoms -//... -``` +The core `atoms` table option is still available in Angular because the adapter re-exports TanStack Table core types. Use it when you already have compatible writable TanStack Store atoms and want a table slice to read from that atom. -#### Fully Controlled State +Most Angular apps should start with Angular signals and the `state` option instead. That keeps ownership in Angular's signal model while `injectTable` keeps table options synchronized with signal changes. -Alternatively, you can control the entire table state with the `onStateChange` table option. It will hoist out the entire table state into your own state management system. Be careful with this approach, as you might find that raising some frequently changing state values up a component tree, like `columnSizingInfo` state`, might cause bad performance issues. +#### External State -A couple of more tricks may be needed to make this work. If you use the `onStateChange` table option, the initial values of the `state` must be populated with all of the relevant state values for all of the features that you want to use. You can either manually type out all of the initial state values, or use a constructor in a special way as shown below. +Use `state` plus `on[State]Change` when Angular should own a table state slice. ```ts - - -class TableComponent { - // create an empty table state, we'll override it later - readonly state = signal({} as TableState); - - // create a table instance with default state values - readonly table = createAngularTable(() => ({ - columns: this.columns, - data: this.data(), - // our fully controlled state overrides the internal state - state: this.state(), - onStateChange: updater => { - // any state changes will be pushed up to our own state management - this.state.set( - updater instanceof Function ? updater(this.state()) : updater - ) - } - })) - - constructor() { - // set the initial table state - this.state.set({ - // populate the initial state with all of the default state values - // from the table instance - ...this.table.initialState, - pagination: { - pageIndex: 0, - pageSize: 15, // optionally customize the initial pagination state. - }, - }) - } -} +readonly sorting = signal([]) +readonly pagination = signal({ + pageIndex: 0, + pageSize: 10, +}) + +readonly table = injectTable(() => ({ + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data: this.data(), + state: { + sorting: this.sorting(), + pagination: this.pagination(), + }, + onSortingChange: (updater) => { + updater instanceof Function + ? this.sorting.update(updater) + : this.sorting.set(updater) + }, + onPaginationChange: (updater) => { + updater instanceof Function + ? this.pagination.update(updater) + : this.pagination.set(updater) + }, +})) ``` -### On State Change Callbacks +The v8-style `onStateChange` option is no longer part of the v9 `injectTable` state model. v9 encourages keeping table state slices atomic and separated for performance. -So far, we have seen the `on[State]Change` and `onStateChange` table options work to "hoist" the table state changes into our own state management. However, there are a few things about these using these options that you should be aware of. +##### On State Change Callbacks -#### 1. **State Change Callbacks MUST have their corresponding state value in the `state` option**. +The `on[State]Change` callbacks are useful when you are controlling a matching slice through the `state` option. They receive either a raw value or an updater function. -Specifying an `on[State]Change` callback tells the table instance that this will be a controlled state. If you do not specify the corresponding `state` value, that state will be "frozen" with its initial value. +If you provide an `on[State]Change` callback, also provide the corresponding value in `state`. For example, `onSortingChange` should be paired with `state.sorting`. ```ts -class TableComponent { - sorting = signal([]) - - table = createAngularTable(() => ({ - columns: this.columns, - data: this.data(), - //... - state: { - sorting: this.sorting(), // required because we are using `onSortingChange` - }, - onSortingChange: updater => { // makes the `state.sorting` controlled - updater instanceof Function - ? this.sorting.update(updater) - : this.sorting.set(updater) - } - })) +onPaginationChange: (updater) => { + updater instanceof Function + ? this.pagination.update(updater) + : this.pagination.set(updater) } ``` -#### 2. **Updaters can either be raw values or callback functions**. - -The `on[State]Change` and `onStateChange` callbacks work exactly like the `setState` functions in React. The updater values can either be a new state value or a callback function that takes the previous state value and returns the new state value. - -What implications does this have? It means that if you want to add in some extra logic in any of the `on[State]Change` callbacks, you can do so, but you need to check whether or not the new incoming updater value is a function or value. +### State Types -This is why you will see the `updater instanceof Function ? this.state.update(updater) : this.state.set(updater)` pattern in the examples above. This pattern checks if the updater is a function, and if it is, it calls the function with the previous state value to get the new state value, or the signal will require `signal.update` to be called with the updater instead of `signal.set`. +Most complex states in TanStack Table have their own TypeScript types that you can import and use. -### State Types +```ts +import { + injectTable, + type PaginationState, + type RowSelectionState, + type SortingState, + type TableState, +} from '@tanstack/angular-table' + +readonly sorting = signal([ + { + id: 'age', + desc: true, + }, +]) +``` -All complex states in TanStack Table have their own TypeScript types that you can import and use. This can be handy for ensuring that you are using the correct data structures and properties for the state values that you are controlling. +`TableState` is inferred from the features registered on that table: ```ts -import {createAngularTable, type SortingState} from '@tanstack/angular-table' - -class TableComponent { - readonly sorting = signal([ - { - id: 'age', // you should get autocomplete for the `id` and `desc` properties - desc: true, - } - ]) -} +type MyTableState = TableState ``` diff --git a/docs/framework/angular/reference/classes/FlexRenderCell.md b/docs/framework/angular/reference/classes/FlexRenderCell.md new file mode 100644 index 0000000000..b54ae8577e --- /dev/null +++ b/docs/framework/angular/reference/classes/FlexRenderCell.md @@ -0,0 +1,101 @@ +--- +id: FlexRenderCell +title: FlexRenderCell +--- + +# Class: FlexRenderCell\ + +Defined in: [helpers/flexRenderCell.ts:62](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/flexRenderCell.ts#L62) + +Simplified directive wrapper of `*flexRender`. + +Use this utility component to render headers, cells, or footers with custom markup. + +Only one prop (`cell`, `header`, or `footer`) may be passed based on the used selector. + +## Examples + +```html +{{cell}} +{{header}} +{{footer}} +``` + +This replaces calling `*flexRender` directly like this: +```html +{{cell}} +{{header}} +{{footer}} +``` + +Can be imported through FlexRenderCell or [FlexRender](../variables/FlexRender.md) import, +which the latter is preferred. + +```ts +import {FlexRender} from '@tanstack/angular-table + +@Component({ + // ... + imports: [ + FlexRender + ] +}) +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +## Constructors + +### Constructor + +```ts +new FlexRenderCell(): FlexRenderCell; +``` + +Defined in: [helpers/flexRenderCell.ts:118](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/flexRenderCell.ts#L118) + +#### Returns + +`FlexRenderCell`\<`TFeatures`, `TData`, `TValue`\> + +## Properties + +### cell + +```ts +readonly cell: InputSignal | undefined>; +``` + +Defined in: [helpers/flexRenderCell.ts:67](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/flexRenderCell.ts#L67) + +*** + +### footer + +```ts +readonly footer: InputSignal | undefined>; +``` + +Defined in: [helpers/flexRenderCell.ts:75](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/flexRenderCell.ts#L75) + +*** + +### header + +```ts +readonly header: InputSignal | undefined>; +``` + +Defined in: [helpers/flexRenderCell.ts:71](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/flexRenderCell.ts#L71) diff --git a/docs/framework/angular/reference/classes/FlexRenderComponentInstance.md b/docs/framework/angular/reference/classes/FlexRenderComponentInstance.md new file mode 100644 index 0000000000..c6b69ad799 --- /dev/null +++ b/docs/framework/angular/reference/classes/FlexRenderComponentInstance.md @@ -0,0 +1,230 @@ +--- +id: FlexRenderComponentInstance +title: FlexRenderComponentInstance +--- + +# Class: FlexRenderComponentInstance\ + +Defined in: [flex-render/flexRenderComponent.ts:259](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flex-render/flexRenderComponent.ts#L259) + +Wrapper class for a component that will be used as content for [FlexRenderDirective](FlexRenderDirective.md) + +Prefer [flexRenderComponent](../functions/flexRenderComponent.md) helper for better type-safety + +## Type Parameters + +### TComponent + +`TComponent` = `any` + +## Implements + +- [`FlexRenderComponent`](../interfaces/FlexRenderComponent.md)\<`TComponent`\> + +## Constructors + +### Constructor + +```ts +new FlexRenderComponentInstance( + component, + inputs?, + injector?, + outputs?, + directives?, +bindings?): FlexRenderComponentInstance; +``` + +Defined in: [flex-render/flexRenderComponent.ts:266](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flex-render/flexRenderComponent.ts#L266) + +#### Parameters + +##### component + +`Type`\<`TComponent`\> + +##### inputs? + +`Inputs`\<`TComponent`\> + +##### injector? + +`Injector` + +##### outputs? + +`Outputs`\<`TComponent`\> + +##### directives? + +(`Type`\<`unknown`\> \| `DirectiveWithBindings`\<`unknown`\>)[] + +##### bindings? + +`Binding`[] + +#### Returns + +`FlexRenderComponentInstance`\<`TComponent`\> + +## Properties + +### allowedInputNames + +```ts +readonly allowedInputNames: string[] = []; +``` + +Defined in: [flex-render/flexRenderComponent.ts:263](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flex-render/flexRenderComponent.ts#L263) + +List of allowed input names. + +#### Implementation of + +[`FlexRenderComponent`](../interfaces/FlexRenderComponent.md).[`allowedInputNames`](../interfaces/FlexRenderComponent.md#allowedinputnames) + +*** + +### allowedOutputNames + +```ts +readonly allowedOutputNames: string[] = []; +``` + +Defined in: [flex-render/flexRenderComponent.ts:264](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flex-render/flexRenderComponent.ts#L264) + +List of allowed output names. + +#### Implementation of + +[`FlexRenderComponent`](../interfaces/FlexRenderComponent.md).[`allowedOutputNames`](../interfaces/FlexRenderComponent.md#allowedoutputnames) + +*** + +### bindings? + +```ts +readonly optional bindings: Binding[]; +``` + +Defined in: [flex-render/flexRenderComponent.ts:272](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flex-render/flexRenderComponent.ts#L272) + +Bindings to apply to the root component + +#### See + +FlexRenderOptions#bindings + +#### Implementation of + +[`FlexRenderComponent`](../interfaces/FlexRenderComponent.md).[`bindings`](../interfaces/FlexRenderComponent.md#bindings) + +*** + +### component + +```ts +readonly component: Type; +``` + +Defined in: [flex-render/flexRenderComponent.ts:267](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flex-render/flexRenderComponent.ts#L267) + +The component type + +#### Implementation of + +[`FlexRenderComponent`](../interfaces/FlexRenderComponent.md).[`component`](../interfaces/FlexRenderComponent.md#component) + +*** + +### directives? + +```ts +readonly optional directives: (Type | DirectiveWithBindings)[]; +``` + +Defined in: [flex-render/flexRenderComponent.ts:271](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flex-render/flexRenderComponent.ts#L271) + +Directives that should be applied to the component. + +#### See + +#### Implementation of + +[`FlexRenderComponent`](../interfaces/FlexRenderComponent.md).[`directives`](../interfaces/FlexRenderComponent.md#directives) + +*** + +### injector? + +```ts +readonly optional injector: Injector; +``` + +Defined in: [flex-render/flexRenderComponent.ts:269](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flex-render/flexRenderComponent.ts#L269) + +Optional Injector that will be used when rendering the component. + +#### See + +FlexRenderOptions#injector + +#### Implementation of + +[`FlexRenderComponent`](../interfaces/FlexRenderComponent.md).[`injector`](../interfaces/FlexRenderComponent.md#injector) + +*** + +### inputs? + +```ts +readonly optional inputs: Inputs; +``` + +Defined in: [flex-render/flexRenderComponent.ts:268](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flex-render/flexRenderComponent.ts#L268) + +Component instance inputs. Set via [componentRef.setInput API](https://angular.dev/api/core/ComponentRef#setInput)) + +#### See + +FlexRenderOptions#inputs + +#### Implementation of + +[`FlexRenderComponent`](../interfaces/FlexRenderComponent.md).[`inputs`](../interfaces/FlexRenderComponent.md#inputs) + +*** + +### mirror + +```ts +readonly mirror: ComponentMirror; +``` + +Defined in: [flex-render/flexRenderComponent.ts:262](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flex-render/flexRenderComponent.ts#L262) + +Reflected metadata about the component. + +#### Implementation of + +[`FlexRenderComponent`](../interfaces/FlexRenderComponent.md).[`mirror`](../interfaces/FlexRenderComponent.md#mirror) + +*** + +### outputs? + +```ts +readonly optional outputs: Outputs; +``` + +Defined in: [flex-render/flexRenderComponent.ts:270](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flex-render/flexRenderComponent.ts#L270) + +Component instance outputs. Subscribed via OutputEmitterRef#subscribe + +#### See + +FlexRenderOptions#outputs + +#### Implementation of + +[`FlexRenderComponent`](../interfaces/FlexRenderComponent.md).[`outputs`](../interfaces/FlexRenderComponent.md#outputs) diff --git a/docs/framework/angular/reference/classes/FlexRenderDirective.md b/docs/framework/angular/reference/classes/FlexRenderDirective.md new file mode 100644 index 0000000000..4431d604f2 --- /dev/null +++ b/docs/framework/angular/reference/classes/FlexRenderDirective.md @@ -0,0 +1,121 @@ +--- +id: FlexRenderDirective +title: FlexRenderDirective +--- + +# Class: FlexRenderDirective\ + +Defined in: [flexRender.ts:84](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flexRender.ts#L84) + +Use this utility directive to render headers, cells, or footers with custom markup. + +Note: If you are rendering cell, header, or footer without custom context or other props, +you can use the [FlexRenderCell](FlexRenderCell.md) directive as shorthand instead . + +## Example + +```ts +import {FlexRender} from '@tanstack/angular-table'; + +@Component({ + imports: [FlexRender], + template: ` + + {{cell}} + + + + {{header}} + + + + {{footer}} + + `, +}) +class App { +} +``` + +Can be imported through FlexRenderDirective or [FlexRender](../variables/FlexRender.md) import, +which the latter is preferred. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TRowData + +`TRowData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### TProps + +`TProps` *extends* + \| `NonNullable`\<`unknown`\> + \| `CellContext`\<`TFeatures`, `TRowData`, `TValue`\> + \| `HeaderContext`\<`TFeatures`, `TRowData`, `TValue`\> + +## Constructors + +### Constructor + +```ts +new FlexRenderDirective(): FlexRenderDirective; +``` + +Defined in: [flexRender.ts:109](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flexRender.ts#L109) + +#### Returns + +`FlexRenderDirective`\<`TFeatures`, `TRowData`, `TValue`, `TProps`\> + +## Properties + +### content + +```ts +readonly content: InputSignal>; +``` + +Defined in: [flexRender.ts:93](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flexRender.ts#L93) + +*** + +### injector + +```ts +readonly injector: InputSignal; +``` + +Defined in: [flexRender.ts:102](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flexRender.ts#L102) + +*** + +### props + +```ts +readonly props: InputSignal; +``` + +Defined in: [flexRender.ts:98](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flexRender.ts#L98) diff --git a/docs/framework/angular/reference/classes/TanStackTable.md b/docs/framework/angular/reference/classes/TanStackTable.md new file mode 100644 index 0000000000..92df3e1fb8 --- /dev/null +++ b/docs/framework/angular/reference/classes/TanStackTable.md @@ -0,0 +1,78 @@ +--- +id: TanStackTable +title: TanStackTable +--- + +# Class: TanStackTable\ + +Defined in: [helpers/table.ts:59](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/table.ts#L59) + +Provides a TanStack Table instance (`AngularTable`) in Angular DI. + +The table can be injected by: +- any descendant of an element using `[tanStackTable]="..."` +- any component instantiated by `*flexRender` when the render props contains `table` + +## Example + +```html +
+ +
+``` + +```ts +@Component({ + selector: 'app-pagination', + template: ` + + + `, +}) +export class PaginationComponent { + readonly table = injectTableContext() + + prev() { + this.table().previousPage() + } + next() { + this.table().nextPage() + } +} +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +## Constructors + +### Constructor + +```ts +new TanStackTable(): TanStackTable; +``` + +#### Returns + +`TanStackTable`\<`TFeatures`, `TData`\> + +## Properties + +### table + +```ts +readonly table: InputSignal>; +``` + +Defined in: [helpers/table.ts:68](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/table.ts#L68) + +The current TanStack Table instance. + +Provided as a required signal input so DI consumers always read the latest value. diff --git a/docs/framework/angular/reference/classes/TanStackTableCell.md b/docs/framework/angular/reference/classes/TanStackTableCell.md new file mode 100644 index 0000000000..985d1475de --- /dev/null +++ b/docs/framework/angular/reference/classes/TanStackTableCell.md @@ -0,0 +1,92 @@ +--- +id: TanStackTableCell +title: TanStackTableCell +--- + +# Class: TanStackTableCell\ + +Defined in: [helpers/cell.ts:76](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/cell.ts#L76) + +Provides a TanStack Table `Cell` instance in Angular DI. + +The cell can be injected by: +- any descendant of an element using `[tanStackTableCell]="..."` +- any component instantiated by `*flexRender` when the render props contains `cell` + +## Examples + +Inject from the nearest `[tanStackTableCell]`: +```html + + + +``` + +```ts +@Component({ + selector: 'app-cell-actions', + template: `{{ cell().id }}`, +}) +export class CellActionsComponent { + readonly cell = injectTableCellContext() +} +``` + +Inject inside a component rendered via `flexRender`: +```ts +@Component({ + selector: 'app-price-cell', + template: `{{ cell().getValue() }}`, +}) +export class PriceCellComponent { + readonly cell = injectTableCellContext() +} +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +## Implements + +- [`TanStackTableCellContext`](../interfaces/TanStackTableCellContext.md)\<`TFeatures`, `TData`, `TValue`\> + +## Constructors + +### Constructor + +```ts +new TanStackTableCell(): TanStackTableCell; +``` + +#### Returns + +`TanStackTableCell`\<`TFeatures`, `TData`, `TValue`\> + +## Properties + +### cell + +```ts +readonly cell: InputSignal>; +``` + +Defined in: [helpers/cell.ts:86](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/cell.ts#L86) + +The current TanStack Table cell. + +Provided as a required signal input so DI consumers always read the latest value. + +#### Implementation of + +[`TanStackTableCellContext`](../interfaces/TanStackTableCellContext.md).[`cell`](../interfaces/TanStackTableCellContext.md#cell) diff --git a/docs/framework/angular/reference/classes/TanStackTableHeader.md b/docs/framework/angular/reference/classes/TanStackTableHeader.md new file mode 100644 index 0000000000..1f1e81e94e --- /dev/null +++ b/docs/framework/angular/reference/classes/TanStackTableHeader.md @@ -0,0 +1,88 @@ +--- +id: TanStackTableHeader +title: TanStackTableHeader +--- + +# Class: TanStackTableHeader\ + +Defined in: [helpers/header.ts:71](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/header.ts#L71) + +Provides a TanStack Table `Header` instance in Angular DI. + +The header can be injected by: +- any descendant of an element using `[tanStackTableHeader]="..."` +- any component instantiated by `*flexRender` when the render props contains `header` + +## Example + +```html + + + +``` + +```ts +@Component({ + selector: 'app-sort-indicator', + template: ` + + `, +}) +export class SortIndicatorComponent { + readonly header = injectTableHeaderContext() + + toggle() { + this.header().column.toggleSorting() + } +} +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +## Implements + +- [`TanStackTableHeaderContext`](../interfaces/TanStackTableHeaderContext.md)\<`TFeatures`, `TData`, `TValue`\> + +## Constructors + +### Constructor + +```ts +new TanStackTableHeader(): TanStackTableHeader; +``` + +#### Returns + +`TanStackTableHeader`\<`TFeatures`, `TData`, `TValue`\> + +## Properties + +### header + +```ts +readonly header: InputSignal>; +``` + +Defined in: [helpers/header.ts:81](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/header.ts#L81) + +The current TanStack Table header. + +Provided as a required signal input so DI consumers always read the latest value. + +#### Implementation of + +[`TanStackTableHeaderContext`](../interfaces/TanStackTableHeaderContext.md).[`header`](../interfaces/TanStackTableHeaderContext.md#header) diff --git a/docs/framework/angular/reference/functions/createTableHook.md b/docs/framework/angular/reference/functions/createTableHook.md new file mode 100644 index 0000000000..74d1c8c39c --- /dev/null +++ b/docs/framework/angular/reference/functions/createTableHook.md @@ -0,0 +1,59 @@ +--- +id: createTableHook +title: createTableHook +--- + +# Function: createTableHook() + +```ts +function createTableHook(__namedParameters): CreateTableHookResult; +``` + +Defined in: [helpers/createTableHook.ts:368](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L368) + +Creates app-scoped Angular table helpers with features, row models, and +renderable component maps pre-bound. + +Use this when an app or design system wants typed `injectAppTable`, +pre-bound column helpers, and typed table/cell/header context injection +helpers without repeating the same feature and component generics. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TTableComponents + +`TTableComponents` *extends* `Record`\<`string`, [`RenderableComponent`](../type-aliases/RenderableComponent.md)\> + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`RenderableComponent`](../type-aliases/RenderableComponent.md)\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`RenderableComponent`](../type-aliases/RenderableComponent.md)\> + +## Parameters + +### \_\_namedParameters + +[`CreateTableContextOptions`](../type-aliases/CreateTableContextOptions.md)\<`TFeatures`, `TTableComponents`, `TCellComponents`, `THeaderComponents`\> + +## Returns + +[`CreateTableHookResult`](../type-aliases/CreateTableHookResult.md)\<`TFeatures`, `TTableComponents`, `TCellComponents`, `THeaderComponents`\> + +## Example + +```ts +const { injectAppTable, createAppColumnHelper } = createTableHook({ + _features, + _rowModels: {}, + tableComponents: {}, + cellComponents: {}, + headerComponents: {}, +}) +``` diff --git a/docs/framework/angular/reference/functions/flexRenderComponent.md b/docs/framework/angular/reference/functions/flexRenderComponent.md new file mode 100644 index 0000000000..b1ba5e998a --- /dev/null +++ b/docs/framework/angular/reference/functions/flexRenderComponent.md @@ -0,0 +1,59 @@ +--- +id: flexRenderComponent +title: flexRenderComponent +--- + +# Function: flexRenderComponent() + +```ts +function flexRenderComponent(component, options?): FlexRenderComponent; +``` + +Defined in: [flex-render/flexRenderComponent.ts:150](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flex-render/flexRenderComponent.ts#L150) + +Helper function to create a [FlexRenderComponent](../interfaces/FlexRenderComponent.md) instance, with better type-safety. + +## Type Parameters + +### TComponent + +`TComponent` = `any` + +## Parameters + +### component + +`Type`\<`TComponent`\> + +### options? + +`FlexRenderOptions`\<`Inputs`\<`TComponent`\>, `Outputs`\<`TComponent`\>\> + +## Returns + +[`FlexRenderComponent`](../interfaces/FlexRenderComponent.md)\<`TComponent`\> + +## Example + +```ts +import {flexRenderComponent} from '@tanstack/angular-table' +import {inputBinding, outputBinding} from '@angular/core'; + +const columns = [ + { + cell: ({ row }) => { + return flexRenderComponent(MyComponent, { + inputs: { value: mySignalValue() }, + outputs: { valueChange: (val) => {} } + // or using angular native createComponent#binding api + bindings: [ + inputBinding('value', mySignalValue), + outputBinding('valueChange', value => { + console.log("my value changed to", value) + }) + ] + }) + }, + }, +] +``` diff --git a/docs/framework/angular/reference/functions/injectFlexRenderContext.md b/docs/framework/angular/reference/functions/injectFlexRenderContext.md new file mode 100644 index 0000000000..18aabf7488 --- /dev/null +++ b/docs/framework/angular/reference/functions/injectFlexRenderContext.md @@ -0,0 +1,26 @@ +--- +id: injectFlexRenderContext +title: injectFlexRenderContext +--- + +# Function: injectFlexRenderContext() + +```ts +function injectFlexRenderContext(): T; +``` + +Defined in: [flex-render/context.ts:12](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flex-render/context.ts#L12) + +Inject the flex render context props. + +Can be used in components rendered via FlexRender directives. + +## Type Parameters + +### T + +`T` *extends* `object` + +## Returns + +`T` diff --git a/docs/framework/angular/reference/functions/injectTable.md b/docs/framework/angular/reference/functions/injectTable.md new file mode 100644 index 0000000000..01716e9fa2 --- /dev/null +++ b/docs/framework/angular/reference/functions/injectTable.md @@ -0,0 +1,88 @@ +--- +id: injectTable +title: injectTable +--- + +# Function: injectTable() + +```ts +function injectTable(options): AngularTable; +``` + +Defined in: [injectTable.ts:91](https://github.com/TanStack/table/blob/main/packages/angular-table/src/injectTable.ts#L91) + +Creates and returns an Angular-reactive table instance. + +The initializer is intentionally re-evaluated whenever any signal read inside it changes. +This is how the adapter keeps the table in sync with Angular's reactivity model. + +Because of that behavior, keep expensive/static values (for example `columns`, feature setup, row models) +as stable references outside the initializer, and only read reactive state (`data()`, pagination/filter/sorting signals, etc.) +inside it. + +The returned table is also signal-reactive: table state and table APIs are wired for Angular signals, so you can safely consume table methods inside `computed(...)` and `effect(...)`. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +## Parameters + +### options + +() => `TableOptions`\<`TFeatures`, `TData`\> + +## Returns + +[`AngularTable`](../type-aliases/AngularTable.md)\<`TFeatures`, `TData`\> + +An Angular-reactive TanStack Table instance. + +## Example + +1. Register the table features you need +```ts +// Register only the features you need +import {tableFeatures, rowPaginationFeature} from '@tanstack/angular-table'; +const _features = tableFeatures({ + rowPaginationFeature, + // ...all other features you need +}) + +// Use all table core features +import {stockFeatures} from '@tanstack/angular-table'; +const _features = tableFeatures(stockFeatures); +``` +2. Prepare the table columns +```ts +import {ColumnDef} from '@tanstack/angular-table'; + +type MyData = {} + +const columns: ColumnDef[] = [ + // ...column definitions +] + +// or using createColumnHelper +import {createColumnHelper} from '@tanstack/angular-table'; +const columnHelper = createColumnHelper(); +const columns = columnHelper.columns([ + columnHelper.accessor(...), + // ...other columns +]) +``` +3. Create the table instance with `injectTable` +```ts +const table = injectTable(() => { + // ...table options, + _features, + columns: columns, + data: myDataSignal(), +}) +``` diff --git a/docs/framework/angular/reference/functions/injectTableCellContext.md b/docs/framework/angular/reference/functions/injectTableCellContext.md new file mode 100644 index 0000000000..99202b535d --- /dev/null +++ b/docs/framework/angular/reference/functions/injectTableCellContext.md @@ -0,0 +1,36 @@ +--- +id: injectTableCellContext +title: injectTableCellContext +--- + +# Function: injectTableCellContext() + +```ts +function injectTableCellContext(): Signal>; +``` + +Defined in: [helpers/cell.ts:98](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/cell.ts#L98) + +Injects the current TanStack Table cell signal. + +Available when: +- there is a nearest `[tanStackTableCell]` directive in the DI tree, or +- the caller is rendered via `*flexRender` with render props containing `cell` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `unknown` + +## Returns + +`Signal`\<`Cell`\<`TFeatures`, `TData`, `TValue`\>\> diff --git a/docs/framework/angular/reference/functions/injectTableContext.md b/docs/framework/angular/reference/functions/injectTableContext.md new file mode 100644 index 0000000000..7ffe58b798 --- /dev/null +++ b/docs/framework/angular/reference/functions/injectTableContext.md @@ -0,0 +1,32 @@ +--- +id: injectTableContext +title: injectTableContext +--- + +# Function: injectTableContext() + +```ts +function injectTableContext(): Signal>; +``` + +Defined in: [helpers/table.ts:80](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/table.ts#L80) + +Injects the current TanStack Table instance signal. + +Available when: +- there is a nearest `[tanStackTable]` directive in the DI tree, or +- the caller is rendered via `*flexRender` with render props containing `table` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +## Returns + +`Signal`\<[`AngularTable`](../type-aliases/AngularTable.md)\<`TFeatures`, `TData`\>\> diff --git a/docs/framework/angular/reference/functions/injectTableHeaderContext.md b/docs/framework/angular/reference/functions/injectTableHeaderContext.md new file mode 100644 index 0000000000..0b3981453a --- /dev/null +++ b/docs/framework/angular/reference/functions/injectTableHeaderContext.md @@ -0,0 +1,36 @@ +--- +id: injectTableHeaderContext +title: injectTableHeaderContext +--- + +# Function: injectTableHeaderContext() + +```ts +function injectTableHeaderContext(): Signal>; +``` + +Defined in: [helpers/header.ts:93](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/header.ts#L93) + +Injects the current TanStack Table header signal. + +Available when: +- there is a nearest `[tanStackTableHeader]` directive in the DI tree, or +- the caller is rendered via `*flexRender` with render props containing `header` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `unknown` + +## Returns + +`Signal`\<`Header`\<`TFeatures`, `TData`, `TValue`\>\> diff --git a/docs/framework/angular/reference/functions/shallow.md b/docs/framework/angular/reference/functions/shallow.md new file mode 100644 index 0000000000..57231700ef --- /dev/null +++ b/docs/framework/angular/reference/functions/shallow.md @@ -0,0 +1,32 @@ +--- +id: shallow +title: shallow +--- + +# Function: shallow() + +```ts +function shallow(objA, objB): boolean; +``` + +Defined in: node\_modules/.pnpm/@tanstack+store@0.11.0/node\_modules/@tanstack/store/dist/shallow.d.ts:2 + +## Type Parameters + +### T + +`T` + +## Parameters + +### objA + +`T` + +### objB + +`T` + +## Returns + +`boolean` diff --git a/docs/framework/angular/reference/index.md b/docs/framework/angular/reference/index.md new file mode 100644 index 0000000000..0166ae210a --- /dev/null +++ b/docs/framework/angular/reference/index.md @@ -0,0 +1,58 @@ +--- +id: "@tanstack/angular-table" +title: "@tanstack/angular-table" +--- + +# @tanstack/angular-table + +## Classes + +- [FlexRenderCell](classes/FlexRenderCell.md) +- [FlexRenderComponentInstance](classes/FlexRenderComponentInstance.md) +- [FlexRenderDirective](classes/FlexRenderDirective.md) +- [TanStackTable](classes/TanStackTable.md) +- [TanStackTableCell](classes/TanStackTableCell.md) +- [TanStackTableHeader](classes/TanStackTableHeader.md) + +## Interfaces + +- [FlexRenderComponent](interfaces/FlexRenderComponent.md) +- [TanStackTableCellContext](interfaces/TanStackTableCellContext.md) +- [TanStackTableHeaderContext](interfaces/TanStackTableHeaderContext.md) + +## Type Aliases + +- [AngularTable](type-aliases/AngularTable.md) +- [AppAngularTable](type-aliases/AppAngularTable.md) +- [AppCellContext](type-aliases/AppCellContext.md) +- [AppColumnDefBase](type-aliases/AppColumnDefBase.md) +- [AppColumnDefTemplate](type-aliases/AppColumnDefTemplate.md) +- [AppColumnHelper](type-aliases/AppColumnHelper.md) +- [AppDisplayColumnDef](type-aliases/AppDisplayColumnDef.md) +- [AppGroupColumnDef](type-aliases/AppGroupColumnDef.md) +- [AppHeaderContext](type-aliases/AppHeaderContext.md) +- [CreateTableContextOptions](type-aliases/CreateTableContextOptions.md) +- [CreateTableHookResult](type-aliases/CreateTableHookResult.md) +- [FlexRenderComponentProps](type-aliases/FlexRenderComponentProps.md) +- [FlexRenderContent](type-aliases/FlexRenderContent.md) +- [FlexRenderInputContent](type-aliases/FlexRenderInputContent.md) +- [RenderableComponent](type-aliases/RenderableComponent.md) +- [SubscribeSource](type-aliases/SubscribeSource.md) + +## Variables + +- [FlexRender](variables/FlexRender.md) +- [TanStackTableCellToken](variables/TanStackTableCellToken.md) +- [TanStackTableHeaderToken](variables/TanStackTableHeaderToken.md) +- [TanStackTableToken](variables/TanStackTableToken.md) + +## Functions + +- [createTableHook](functions/createTableHook.md) +- [flexRenderComponent](functions/flexRenderComponent.md) +- [injectFlexRenderContext](functions/injectFlexRenderContext.md) +- [injectTable](functions/injectTable.md) +- [injectTableCellContext](functions/injectTableCellContext.md) +- [injectTableContext](functions/injectTableContext.md) +- [injectTableHeaderContext](functions/injectTableHeaderContext.md) +- [shallow](functions/shallow.md) diff --git a/docs/framework/angular/reference/interfaces/FlexRenderComponent.md b/docs/framework/angular/reference/interfaces/FlexRenderComponent.md new file mode 100644 index 0000000000..8c5516f86f --- /dev/null +++ b/docs/framework/angular/reference/interfaces/FlexRenderComponent.md @@ -0,0 +1,179 @@ +--- +id: FlexRenderComponent +title: FlexRenderComponent +--- + +# Interface: FlexRenderComponent\ + +Defined in: [flex-render/flexRenderComponent.ts:205](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flex-render/flexRenderComponent.ts#L205) + +Wrapper interface for a component that will be used as content for [FlexRenderDirective](../classes/FlexRenderDirective.md). +Can be created using [flexRenderComponent](../functions/flexRenderComponent.md) helper. + +## Example + +```ts +import {flexRenderComponent} from '@tanstack/angular-table' + +// Usage in cell/header/footer definition +const columns = [ + { + cell: ({ row }) => { + return flexRenderComponent(MyComponent, { + inputs: { value: mySignalValue() }, + outputs: { valueChange: (val) => {} } + // or using angular createComponent#bindings api + bindings: [ + inputBinding('value', mySignalValue), + outputBinding('valueChange', value => { + console.log("my value changed to", value) + }) + ] + }) + }, + }, +] + +import {input, output} from '@angular/core'; + +@Component({ + selector: 'my-component', +}) +class MyComponent { + readonly value = input(0); + readonly valueChange = output(); +} + +``` + +## Type Parameters + +### TComponent + +`TComponent` = `any` + +## Properties + +### allowedInputNames + +```ts +readonly allowedInputNames: string[]; +``` + +Defined in: [flex-render/flexRenderComponent.ts:217](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flex-render/flexRenderComponent.ts#L217) + +List of allowed input names. + +*** + +### allowedOutputNames + +```ts +readonly allowedOutputNames: string[]; +``` + +Defined in: [flex-render/flexRenderComponent.ts:221](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flex-render/flexRenderComponent.ts#L221) + +List of allowed output names. + +*** + +### bindings? + +```ts +optional bindings: Binding[]; +``` + +Defined in: [flex-render/flexRenderComponent.ts:245](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flex-render/flexRenderComponent.ts#L245) + +Bindings to apply to the root component + +#### See + +FlexRenderOptions#bindings + +*** + +### component + +```ts +readonly component: Type; +``` + +Defined in: [flex-render/flexRenderComponent.ts:209](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flex-render/flexRenderComponent.ts#L209) + +The component type + +*** + +### directives? + +```ts +optional directives: (Type | DirectiveWithBindings)[]; +``` + +Defined in: [flex-render/flexRenderComponent.ts:251](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flex-render/flexRenderComponent.ts#L251) + +Directives that should be applied to the component. + +#### See + +*** + +### injector? + +```ts +readonly optional injector: Injector; +``` + +Defined in: [flex-render/flexRenderComponent.ts:239](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flex-render/flexRenderComponent.ts#L239) + +Optional Injector that will be used when rendering the component. + +#### See + +FlexRenderOptions#injector + +*** + +### inputs? + +```ts +readonly optional inputs: Inputs; +``` + +Defined in: [flex-render/flexRenderComponent.ts:233](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flex-render/flexRenderComponent.ts#L233) + +Component instance inputs. Set via [componentRef.setInput API](https://angular.dev/api/core/ComponentRef#setInput)) + +#### See + +FlexRenderOptions#inputs + +*** + +### mirror + +```ts +readonly mirror: ComponentMirror; +``` + +Defined in: [flex-render/flexRenderComponent.ts:213](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flex-render/flexRenderComponent.ts#L213) + +Reflected metadata about the component. + +*** + +### outputs? + +```ts +readonly optional outputs: Outputs; +``` + +Defined in: [flex-render/flexRenderComponent.ts:227](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flex-render/flexRenderComponent.ts#L227) + +Component instance outputs. Subscribed via OutputEmitterRef#subscribe + +#### See + +FlexRenderOptions#outputs diff --git a/docs/framework/angular/reference/interfaces/TanStackTableCellContext.md b/docs/framework/angular/reference/interfaces/TanStackTableCellContext.md new file mode 100644 index 0000000000..a81f140e97 --- /dev/null +++ b/docs/framework/angular/reference/interfaces/TanStackTableCellContext.md @@ -0,0 +1,39 @@ +--- +id: TanStackTableCellContext +title: TanStackTableCellContext +--- + +# Interface: TanStackTableCellContext\ + +Defined in: [helpers/cell.ts:11](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/cell.ts#L11) + +DI context shape for a TanStack Table cell. + +This exists to make the current `Cell` injectable by any nested component/directive +without having to pass it through inputs/props manually. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +## Properties + +### cell + +```ts +cell: Signal>; +``` + +Defined in: [helpers/cell.ts:17](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/cell.ts#L17) + +Signal that returns the current cell instance. diff --git a/docs/framework/angular/reference/interfaces/TanStackTableHeaderContext.md b/docs/framework/angular/reference/interfaces/TanStackTableHeaderContext.md new file mode 100644 index 0000000000..4b48ff47e8 --- /dev/null +++ b/docs/framework/angular/reference/interfaces/TanStackTableHeaderContext.md @@ -0,0 +1,39 @@ +--- +id: TanStackTableHeaderContext +title: TanStackTableHeaderContext +--- + +# Interface: TanStackTableHeaderContext\ + +Defined in: [helpers/header.ts:11](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/header.ts#L11) + +DI context shape for a TanStack Table header. + +This exists to make the current `Header` injectable by any nested component/directive +without passing it through inputs/props. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +## Properties + +### header + +```ts +header: Signal>; +``` + +Defined in: [helpers/header.ts:17](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/header.ts#L17) + +Signal that returns the current header instance. diff --git a/docs/framework/angular/reference/type-aliases/AngularTable.md b/docs/framework/angular/reference/type-aliases/AngularTable.md new file mode 100644 index 0000000000..dd4dc56ce9 --- /dev/null +++ b/docs/framework/angular/reference/type-aliases/AngularTable.md @@ -0,0 +1,22 @@ +--- +id: AngularTable +title: AngularTable +--- + +# Type Alias: AngularTable\ + +```ts +type AngularTable = Table; +``` + +Defined in: [injectTable.ts:30](https://github.com/TanStack/table/blob/main/packages/angular-table/src/injectTable.ts#L30) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` diff --git a/docs/framework/angular/reference/type-aliases/AppAngularTable.md b/docs/framework/angular/reference/type-aliases/AppAngularTable.md new file mode 100644 index 0000000000..e60ea2c37c --- /dev/null +++ b/docs/framework/angular/reference/type-aliases/AppAngularTable.md @@ -0,0 +1,104 @@ +--- +id: AppAngularTable +title: AppAngularTable +--- + +# Type Alias: AppAngularTable\ + +```ts +type AppAngularTable = AngularTable & NoInfer & object; +``` + +Defined in: [helpers/createTableHook.ts:243](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L243) + +Extended table API returned by useAppTable with all App wrapper components + +## Type Declaration + +### appCell() + +```ts +appCell: (cell) => Cell & NoInfer; +``` + +#### Type Parameters + +##### TValue + +`TValue` + +#### Parameters + +##### cell + +`Cell`\<`TFeatures`, `TData`, `TValue`\> + +#### Returns + +`Cell`\<`TFeatures`, `TData`, `TValue`\> & `NoInfer`\<`TCellComponents`\> + +### appFooter() + +```ts +appFooter: (footer) => Header & NoInfer; +``` + +#### Type Parameters + +##### TValue + +`TValue` + +#### Parameters + +##### footer + +`Header`\<`TFeatures`, `TData`, `TValue`\> + +#### Returns + +`Header`\<`TFeatures`, `TData`, `TValue`\> & `NoInfer`\<`THeaderComponents`\> + +### appHeader() + +```ts +appHeader: (header) => Header & NoInfer; +``` + +#### Type Parameters + +##### TValue + +`TValue` + +#### Parameters + +##### header + +`Header`\<`TFeatures`, `TData`, `TValue`\> + +#### Returns + +`Header`\<`TFeatures`, `TData`, `TValue`\> & `NoInfer`\<`THeaderComponents`\> + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TTableComponents + +`TTableComponents` *extends* `Record`\<`string`, [`RenderableComponent`](RenderableComponent.md)\> + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`RenderableComponent`](RenderableComponent.md)\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`RenderableComponent`](RenderableComponent.md)\> diff --git a/docs/framework/angular/reference/type-aliases/AppCellContext.md b/docs/framework/angular/reference/type-aliases/AppCellContext.md new file mode 100644 index 0000000000..86535b0aca --- /dev/null +++ b/docs/framework/angular/reference/type-aliases/AppCellContext.md @@ -0,0 +1,105 @@ +--- +id: AppCellContext +title: AppCellContext +--- + +# Type Alias: AppCellContext\ + +```ts +type AppCellContext = object; +``` + +Defined in: [helpers/createTableHook.ts:47](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L47) + +Enhanced CellContext with pre-bound cell components. +The `cell` property includes the registered cellComponents. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`RenderableComponent`](RenderableComponent.md)\> + +## Properties + +### cell + +```ts +cell: Cell & TCellComponents & object; +``` + +Defined in: [helpers/createTableHook.ts:53](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L53) + +#### Type Declaration + +##### FlexRender() + +```ts +FlexRender: () => unknown; +``` + +###### Returns + +`unknown` + +*** + +### column + +```ts +column: Column; +``` + +Defined in: [helpers/createTableHook.ts:55](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L55) + +*** + +### getValue + +```ts +getValue: CellContext["getValue"]; +``` + +Defined in: [helpers/createTableHook.ts:56](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L56) + +*** + +### renderValue + +```ts +renderValue: CellContext["renderValue"]; +``` + +Defined in: [helpers/createTableHook.ts:57](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L57) + +*** + +### row + +```ts +row: Row; +``` + +Defined in: [helpers/createTableHook.ts:58](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L58) + +*** + +### table + +```ts +table: Table; +``` + +Defined in: [helpers/createTableHook.ts:59](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L59) diff --git a/docs/framework/angular/reference/type-aliases/AppColumnDefBase.md b/docs/framework/angular/reference/type-aliases/AppColumnDefBase.md new file mode 100644 index 0000000000..2d76e21b24 --- /dev/null +++ b/docs/framework/angular/reference/type-aliases/AppColumnDefBase.md @@ -0,0 +1,56 @@ +--- +id: AppColumnDefBase +title: AppColumnDefBase +--- + +# Type Alias: AppColumnDefBase\ + +```ts +type AppColumnDefBase = Omit, "cell" | "header" | "footer"> & object; +``` + +Defined in: [helpers/createTableHook.ts:92](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L92) + +Enhanced column definition base with pre-bound components in cell/header/footer contexts. + +## Type Declaration + +### cell? + +```ts +optional cell: AppColumnDefTemplate>; +``` + +### footer? + +```ts +optional footer: AppColumnDefTemplate>; +``` + +### header? + +```ts +optional header: AppColumnDefTemplate>; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`RenderableComponent`](RenderableComponent.md)\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`RenderableComponent`](RenderableComponent.md)\> diff --git a/docs/framework/angular/reference/type-aliases/AppColumnDefTemplate.md b/docs/framework/angular/reference/type-aliases/AppColumnDefTemplate.md new file mode 100644 index 0000000000..fbf096fcae --- /dev/null +++ b/docs/framework/angular/reference/type-aliases/AppColumnDefTemplate.md @@ -0,0 +1,20 @@ +--- +id: AppColumnDefTemplate +title: AppColumnDefTemplate +--- + +# Type Alias: AppColumnDefTemplate\ + +```ts +type AppColumnDefTemplate = string | (props) => any; +``` + +Defined in: [helpers/createTableHook.ts:85](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L85) + +Template type for column definitions that can be a string or a function. + +## Type Parameters + +### TProps + +`TProps` *extends* `object` diff --git a/docs/framework/angular/reference/type-aliases/AppColumnHelper.md b/docs/framework/angular/reference/type-aliases/AppColumnHelper.md new file mode 100644 index 0000000000..a45c8e94c7 --- /dev/null +++ b/docs/framework/angular/reference/type-aliases/AppColumnHelper.md @@ -0,0 +1,144 @@ +--- +id: AppColumnHelper +title: AppColumnHelper +--- + +# Type Alias: AppColumnHelper\ + +```ts +type AppColumnHelper = object; +``` + +Defined in: [helpers/createTableHook.ts:168](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L168) + +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 Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`RenderableComponent`](RenderableComponent.md)\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`RenderableComponent`](RenderableComponent.md)\> + +## Properties + +### accessor() + +```ts +accessor: (accessor, column) => TAccessor extends AccessorFn ? AccessorFnColumnDef : AccessorKeyColumnDef; +``` + +Defined in: [helpers/createTableHook.ts:178](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L178) + +Creates a data column definition with an accessor key or function. +The cell, header, and footer contexts include pre-bound components. + +#### Type Parameters + +##### TAccessor + +`TAccessor` *extends* `AccessorFn`\<`TData`\> \| `DeepKeys`\<`TData`\> + +##### TValue + +`TValue` *extends* `TAccessor` *extends* `AccessorFn`\<`TData`, infer TReturn\> ? `TReturn` : `TAccessor` *extends* `DeepKeys`\<`TData`\> ? `DeepValue`\<`TData`, `TAccessor`\> : `never` + +#### Parameters + +##### accessor + +`TAccessor` + +##### column + +`TAccessor` *extends* `AccessorFn`\<`TData`\> ? [`AppColumnDefBase`](AppColumnDefBase.md)\<`TFeatures`, `TData`, `TValue`, `TCellComponents`, `THeaderComponents`\> & `object` : [`AppColumnDefBase`](AppColumnDefBase.md)\<`TFeatures`, `TData`, `TValue`, `TCellComponents`, `THeaderComponents`\> + +#### Returns + +`TAccessor` *extends* `AccessorFn`\<`TData`\> ? `AccessorFnColumnDef`\<`TFeatures`, `TData`, `TValue`\> : `AccessorKeyColumnDef`\<`TFeatures`, `TData`, `TValue`\> + +*** + +### columns() + +```ts +columns: (columns) => ColumnDef[] & [...TColumns]; +``` + +Defined in: [helpers/createTableHook.ts:209](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L209) + +Wraps an array of column definitions to preserve each column's individual TValue type. + +#### Type Parameters + +##### TColumns + +`TColumns` *extends* `ReadonlyArray`\<`ColumnDef`\<`TFeatures`, `TData`, `any`\>\> + +#### Parameters + +##### columns + +\[`...TColumns`\] + +#### Returns + +`ColumnDef`\<`TFeatures`, `TData`, `any`\>[] & \[`...TColumns`\] + +*** + +### display() + +```ts +display: (column) => DisplayColumnDef; +``` + +Defined in: [helpers/createTableHook.ts:217](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L217) + +Creates a display column definition for non-data columns. +The cell, header, and footer contexts include pre-bound components. + +#### Parameters + +##### column + +[`AppDisplayColumnDef`](AppDisplayColumnDef.md)\<`TFeatures`, `TData`, `TCellComponents`, `THeaderComponents`\> + +#### Returns + +`DisplayColumnDef`\<`TFeatures`, `TData`, `unknown`\> + +*** + +### group() + +```ts +group: (column) => GroupColumnDef; +``` + +Defined in: [helpers/createTableHook.ts:230](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L230) + +Creates a group column definition with nested child columns. +The cell, header, and footer contexts include pre-bound components. + +#### Parameters + +##### column + +[`AppGroupColumnDef`](AppGroupColumnDef.md)\<`TFeatures`, `TData`, `TCellComponents`, `THeaderComponents`\> + +#### Returns + +`GroupColumnDef`\<`TFeatures`, `TData`, `unknown`\> diff --git a/docs/framework/angular/reference/type-aliases/AppDisplayColumnDef.md b/docs/framework/angular/reference/type-aliases/AppDisplayColumnDef.md new file mode 100644 index 0000000000..3a4aa80390 --- /dev/null +++ b/docs/framework/angular/reference/type-aliases/AppDisplayColumnDef.md @@ -0,0 +1,52 @@ +--- +id: AppDisplayColumnDef +title: AppDisplayColumnDef +--- + +# Type Alias: AppDisplayColumnDef\ + +```ts +type AppDisplayColumnDef = Omit, "cell" | "header" | "footer"> & object; +``` + +Defined in: [helpers/createTableHook.ts:116](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L116) + +Enhanced display column definition with pre-bound components. + +## Type Declaration + +### cell? + +```ts +optional cell: AppColumnDefTemplate>; +``` + +### footer? + +```ts +optional footer: AppColumnDefTemplate>; +``` + +### header? + +```ts +optional header: AppColumnDefTemplate>; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`RenderableComponent`](RenderableComponent.md)\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`RenderableComponent`](RenderableComponent.md)\> diff --git a/docs/framework/angular/reference/type-aliases/AppGroupColumnDef.md b/docs/framework/angular/reference/type-aliases/AppGroupColumnDef.md new file mode 100644 index 0000000000..0eb406b104 --- /dev/null +++ b/docs/framework/angular/reference/type-aliases/AppGroupColumnDef.md @@ -0,0 +1,58 @@ +--- +id: AppGroupColumnDef +title: AppGroupColumnDef +--- + +# Type Alias: AppGroupColumnDef\ + +```ts +type AppGroupColumnDef = Omit, "cell" | "header" | "footer" | "columns"> & object; +``` + +Defined in: [helpers/createTableHook.ts:139](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L139) + +Enhanced group column definition with pre-bound components. + +## Type Declaration + +### cell? + +```ts +optional cell: AppColumnDefTemplate>; +``` + +### columns? + +```ts +optional columns: ReadonlyArray>; +``` + +### footer? + +```ts +optional footer: AppColumnDefTemplate>; +``` + +### header? + +```ts +optional header: AppColumnDefTemplate>; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`RenderableComponent`](RenderableComponent.md)\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`RenderableComponent`](RenderableComponent.md)\> diff --git a/docs/framework/angular/reference/type-aliases/AppHeaderContext.md b/docs/framework/angular/reference/type-aliases/AppHeaderContext.md new file mode 100644 index 0000000000..5b11ba35ef --- /dev/null +++ b/docs/framework/angular/reference/type-aliases/AppHeaderContext.md @@ -0,0 +1,75 @@ +--- +id: AppHeaderContext +title: AppHeaderContext +--- + +# Type Alias: AppHeaderContext\ + +```ts +type AppHeaderContext = object; +``` + +Defined in: [helpers/createTableHook.ts:66](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L66) + +Enhanced HeaderContext with pre-bound header components. +The `header` property includes the registered headerComponents. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`RenderableComponent`](RenderableComponent.md)\> + +## Properties + +### column + +```ts +column: Column; +``` + +Defined in: [helpers/createTableHook.ts:72](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L72) + +*** + +### header + +```ts +header: Header & THeaderComponents & object; +``` + +Defined in: [helpers/createTableHook.ts:73](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L73) + +#### Type Declaration + +##### FlexRender() + +```ts +FlexRender: () => unknown; +``` + +###### Returns + +`unknown` + +*** + +### table + +```ts +table: Table; +``` + +Defined in: [helpers/createTableHook.ts:75](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L75) diff --git a/docs/framework/angular/reference/type-aliases/CreateTableContextOptions.md b/docs/framework/angular/reference/type-aliases/CreateTableContextOptions.md new file mode 100644 index 0000000000..a07dad6a83 --- /dev/null +++ b/docs/framework/angular/reference/type-aliases/CreateTableContextOptions.md @@ -0,0 +1,83 @@ +--- +id: CreateTableContextOptions +title: CreateTableContextOptions +--- + +# Type Alias: CreateTableContextOptions\ + +```ts +type CreateTableContextOptions = Omit, "columns" | "data" | "store" | "state" | "initialState"> & object; +``` + +Defined in: [helpers/createTableHook.ts:272](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L272) + +Options for creating a table hook with pre-bound components and default table options. +Extends all TableOptions except 'columns' | 'data' | 'store' | 'state' | 'initialState'. + +## Type Declaration + +### cellComponents? + +```ts +optional cellComponents: TCellComponents; +``` + +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 + +```ts +{ TextCell, NumberCell, DateCell, CurrencyCell } +``` + +### headerComponents? + +```ts +optional headerComponents: THeaderComponents; +``` + +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 + +```ts +{ SortIndicator, ColumnFilter, ResizeHandle } +``` + +### tableComponents? + +```ts +optional tableComponents: TTableComponents; +``` + +Table-level components that need access to the table instance. +These are available directly on the table object returned by useAppTable. +Use `useTableContext()` inside these components. + +#### Example + +```ts +{ PaginationControls, GlobalFilter, RowCount } +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TTableComponents + +`TTableComponents` *extends* `Record`\<`string`, [`RenderableComponent`](RenderableComponent.md)\> + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`RenderableComponent`](RenderableComponent.md)\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`RenderableComponent`](RenderableComponent.md)\> diff --git a/docs/framework/angular/reference/type-aliases/CreateTableHookResult.md b/docs/framework/angular/reference/type-aliases/CreateTableHookResult.md new file mode 100644 index 0000000000..3202c88d81 --- /dev/null +++ b/docs/framework/angular/reference/type-aliases/CreateTableHookResult.md @@ -0,0 +1,192 @@ +--- +id: CreateTableHookResult +title: CreateTableHookResult +--- + +# Type Alias: CreateTableHookResult\ + +```ts +type CreateTableHookResult = object; +``` + +Defined in: [helpers/createTableHook.ts:304](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L304) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TTableComponents + +`TTableComponents` *extends* `Record`\<`string`, [`RenderableComponent`](RenderableComponent.md)\> + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`RenderableComponent`](RenderableComponent.md)\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`RenderableComponent`](RenderableComponent.md)\> + +## Properties + +### createAppColumnHelper() + +```ts +createAppColumnHelper: () => AppColumnHelper; +``` + +Defined in: [helpers/createTableHook.ts:310](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L310) + +#### Type Parameters + +##### TData + +`TData` *extends* `RowData` + +#### Returns + +[`AppColumnHelper`](AppColumnHelper.md)\<`TFeatures`, `TData`, `TCellComponents`, `THeaderComponents`\> + +*** + +### injectAppTable() + +```ts +injectAppTable: (tableOptions) => AppAngularTable; +``` + +Defined in: [helpers/createTableHook.ts:335](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L335) + +#### Type Parameters + +##### TData + +`TData` *extends* `RowData` + +#### Parameters + +##### tableOptions + +() => `Omit`\<`TableOptions`\<`TFeatures`, `TData`\>, `"_features"` \| `"_rowModels"`\> + +#### Returns + +[`AppAngularTable`](AppAngularTable.md)\<`TFeatures`, `TData`, `TTableComponents`, `TCellComponents`, `THeaderComponents`\> + +*** + +### injectFlexRenderCellContext() + +```ts +injectFlexRenderCellContext: () => CellContext; +``` + +Defined in: [helpers/createTableHook.ts:331](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L331) + +#### Type Parameters + +##### TData + +`TData` *extends* `RowData` + +##### TValue + +`TValue` *extends* `CellData` + +#### Returns + +`CellContext`\<`TFeatures`, `TData`, `TValue`\> + +*** + +### injectFlexRenderHeaderContext() + +```ts +injectFlexRenderHeaderContext: () => HeaderContext; +``` + +Defined in: [helpers/createTableHook.ts:327](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L327) + +#### Type Parameters + +##### TData + +`TData` *extends* `RowData` + +##### TValue + +`TValue` *extends* `CellData` + +#### Returns + +`HeaderContext`\<`TFeatures`, `TData`, `TValue`\> + +*** + +### injectTableCellContext() + +```ts +injectTableCellContext: () => Signal>; +``` + +Defined in: [helpers/createTableHook.ts:323](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L323) + +#### Type Parameters + +##### TValue + +`TValue` *extends* `CellData` = `CellData` + +##### TRowData + +`TRowData` *extends* `RowData` = `RowData` + +#### Returns + +`Signal`\<`Cell`\<`TFeatures`, `TRowData`, `TValue`\>\> + +*** + +### injectTableContext() + +```ts +injectTableContext: () => Signal>; +``` + +Defined in: [helpers/createTableHook.ts:316](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L316) + +#### Type Parameters + +##### TData + +`TData` *extends* `RowData` = `RowData` + +#### Returns + +`Signal`\<[`AngularTable`](AngularTable.md)\<`TFeatures`, `TData`\>\> + +*** + +### injectTableHeaderContext() + +```ts +injectTableHeaderContext: () => Signal>; +``` + +Defined in: [helpers/createTableHook.ts:319](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L319) + +#### Type Parameters + +##### TValue + +`TValue` *extends* `CellData` = `CellData` + +##### TRowData + +`TRowData` *extends* `RowData` = `RowData` + +#### Returns + +`Signal`\<`Header`\<`TFeatures`, `TRowData`, `TValue`\>\> diff --git a/docs/framework/angular/reference/type-aliases/FlexRenderComponentProps.md b/docs/framework/angular/reference/type-aliases/FlexRenderComponentProps.md new file mode 100644 index 0000000000..14ba2f95e1 --- /dev/null +++ b/docs/framework/angular/reference/type-aliases/FlexRenderComponentProps.md @@ -0,0 +1,13 @@ +--- +id: FlexRenderComponentProps +title: FlexRenderComponentProps +--- + +# Type Alias: FlexRenderComponentProps + +```ts +type FlexRenderComponentProps = InjectionToken<{ +}>; +``` + +Defined in: [flex-render/context.ts:3](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flex-render/context.ts#L3) diff --git a/docs/framework/angular/reference/type-aliases/FlexRenderContent.md b/docs/framework/angular/reference/type-aliases/FlexRenderContent.md new file mode 100644 index 0000000000..a4768d94a4 --- /dev/null +++ b/docs/framework/angular/reference/type-aliases/FlexRenderContent.md @@ -0,0 +1,31 @@ +--- +id: FlexRenderContent +title: FlexRenderContent +--- + +# Type Alias: FlexRenderContent\ + +```ts +type FlexRenderContent = + | string + | number + | Type + | FlexRenderComponent + | TemplateRef<{ + $implicit: TProps; +}> + | null + | Record + | undefined; +``` + +Defined in: [flex-render/renderer.ts:44](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flex-render/renderer.ts#L44) + +Content supported by the `flexRender` directive when declaring +a table column header/cell. + +## Type Parameters + +### TProps + +`TProps` *extends* `NonNullable`\<`unknown`\> diff --git a/docs/framework/angular/reference/type-aliases/FlexRenderInputContent.md b/docs/framework/angular/reference/type-aliases/FlexRenderInputContent.md new file mode 100644 index 0000000000..5866c8a25c --- /dev/null +++ b/docs/framework/angular/reference/type-aliases/FlexRenderInputContent.md @@ -0,0 +1,25 @@ +--- +id: FlexRenderInputContent +title: FlexRenderInputContent +--- + +# Type Alias: FlexRenderInputContent\ + +```ts +type FlexRenderInputContent = + | number + | string + | (props) => FlexRenderContent + | null + | undefined; +``` + +Defined in: [flex-render/renderer.ts:57](https://github.com/TanStack/table/blob/main/packages/angular-table/src/flex-render/renderer.ts#L57) + +Input content supported by the `flexRender` directives. + +## Type Parameters + +### TProps + +`TProps` *extends* `NonNullable`\<`unknown`\> diff --git a/docs/framework/angular/reference/type-aliases/RenderableComponent.md b/docs/framework/angular/reference/type-aliases/RenderableComponent.md new file mode 100644 index 0000000000..a5f586d6fa --- /dev/null +++ b/docs/framework/angular/reference/type-aliases/RenderableComponent.md @@ -0,0 +1,14 @@ +--- +id: RenderableComponent +title: RenderableComponent +--- + +# Type Alias: RenderableComponent + +```ts +type RenderableComponent = + | Type +| (props) => FlexRenderContent; +``` + +Defined in: [helpers/createTableHook.ts:35](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/createTableHook.ts#L35) diff --git a/docs/framework/angular/reference/type-aliases/SubscribeSource.md b/docs/framework/angular/reference/type-aliases/SubscribeSource.md new file mode 100644 index 0000000000..ff1c093cf2 --- /dev/null +++ b/docs/framework/angular/reference/type-aliases/SubscribeSource.md @@ -0,0 +1,22 @@ +--- +id: SubscribeSource +title: SubscribeSource +--- + +# Type Alias: SubscribeSource\ + +```ts +type SubscribeSource = + | Atom + | ReadonlyAtom + | Store +| ReadonlyStore; +``` + +Defined in: [injectTable.ts:24](https://github.com/TanStack/table/blob/main/packages/angular-table/src/injectTable.ts#L24) + +## Type Parameters + +### TValue + +`TValue` diff --git a/docs/framework/angular/reference/variables/FlexRender.md b/docs/framework/angular/reference/variables/FlexRender.md new file mode 100644 index 0000000000..e945f3ef4c --- /dev/null +++ b/docs/framework/angular/reference/variables/FlexRender.md @@ -0,0 +1,21 @@ +--- +id: FlexRender +title: FlexRender +--- + +# Variable: FlexRender + +```ts +const FlexRender: readonly [typeof FlexRenderDirective, typeof FlexRenderCell]; +``` + +Defined in: [index.ts:24](https://github.com/TanStack/table/blob/main/packages/angular-table/src/index.ts#L24) + +Constant helper to import FlexRender directives. + +You should prefer to use this constant over importing the directives separately, +as it ensures you always have the correct set of directives over library updates. + +## See + +[FlexRenderDirective](../classes/FlexRenderDirective.md) and [FlexRenderCell](../classes/FlexRenderCell.md) for more details on the directives included in this export. diff --git a/docs/framework/angular/reference/variables/TanStackTableCellToken.md b/docs/framework/angular/reference/variables/TanStackTableCellToken.md new file mode 100644 index 0000000000..0074737e5e --- /dev/null +++ b/docs/framework/angular/reference/variables/TanStackTableCellToken.md @@ -0,0 +1,16 @@ +--- +id: TanStackTableCellToken +title: TanStackTableCellToken +--- + +# Variable: TanStackTableCellToken + +```ts +const TanStackTableCellToken: InjectionToken>; +``` + +Defined in: [helpers/cell.ts:25](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/cell.ts#L25) + +Injection token that provides access to the current cell. + +This token is provided by the [TanStackTableCell](../classes/TanStackTableCell.md) directive. diff --git a/docs/framework/angular/reference/variables/TanStackTableHeaderToken.md b/docs/framework/angular/reference/variables/TanStackTableHeaderToken.md new file mode 100644 index 0000000000..985af1fcc1 --- /dev/null +++ b/docs/framework/angular/reference/variables/TanStackTableHeaderToken.md @@ -0,0 +1,16 @@ +--- +id: TanStackTableHeaderToken +title: TanStackTableHeaderToken +--- + +# Variable: TanStackTableHeaderToken + +```ts +const TanStackTableHeaderToken: InjectionToken>; +``` + +Defined in: [helpers/header.ts:25](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/header.ts#L25) + +Injection token that provides access to the current header. + +This token is provided by the [TanStackTableHeader](../classes/TanStackTableHeader.md) directive. diff --git a/docs/framework/angular/reference/variables/TanStackTableToken.md b/docs/framework/angular/reference/variables/TanStackTableToken.md new file mode 100644 index 0000000000..1c20190368 --- /dev/null +++ b/docs/framework/angular/reference/variables/TanStackTableToken.md @@ -0,0 +1,16 @@ +--- +id: TanStackTableToken +title: TanStackTableToken +--- + +# Variable: TanStackTableToken + +```ts +const TanStackTableToken: InjectionToken>; +``` + +Defined in: [helpers/table.ts:11](https://github.com/TanStack/table/blob/main/packages/angular-table/src/helpers/table.ts#L11) + +Injection token that provides access to the current [AngularTable](../type-aliases/AngularTable.md) instance. + +This token is provided by the [TanStackTable](../classes/TanStackTable.md) directive. diff --git a/docs/framework/lit/guide/table-state.md b/docs/framework/lit/guide/table-state.md index 6aee7092da..7a4b8d41a9 100644 --- a/docs/framework/lit/guide/table-state.md +++ b/docs/framework/lit/guide/table-state.md @@ -2,192 +2,314 @@ title: Table State (Lit) Guide --- +## Examples + +Want to skip to the implementation? Check out these examples: + +- [Basic TableController](../examples/basic-table-controller) +- [Basic App Table](../examples/basic-app-table) +- [Basic External Atoms](../examples/basic-external-atoms) +- [Basic External State](../examples/basic-external-state) + ## Table State (Lit) Guide -TanStack Table has a simple underlying internal state management system to store and manage the state of the table. It also lets you selectively pull out any state that you need to manage in your own state management. This guide will walk you through the different ways in which you can interact with and manage the state of the table. +> **If you boil TanStack Table down to one sentence: TanStack Table is a large state-management coordinator for table states.** + +Understanding this guide is fundamental to understanding how TanStack Table works and how to interact with it for the best results. + +### Do you need to Manage External State? + +You usually do NOT need to manage table state yourself. If you pass nothing to `initialState`, `atoms`, `state`, or any of the `on[State]Change` table options, TanStack Table will manage its own state internally. + +There will be situations where you need to customize how you interact with the internal table state, or even hoist it up to your own scopes. TanStack Table lets you read, subscribe to, or own the state slices that matter to your app. This guide explains how table state works in Lit, how to read it, and when to use external atoms or external state. + +### State in v9 + +TanStack Table v9 overhauled state management around TanStack Store. TanStack Store uses the `alien-signals` implementation and supports performant derived state. For Lit, the table adapter wires TanStack Store atoms into a `TableController`. + +A table instance has a few state surfaces: + +- `table.baseAtoms` are the internal writable atoms created from the resolved initial state. +- `table.atoms` are readonly derived atoms exposed per registered state slice. +- `table.store` is a readonly flat TanStack Store derived by putting all of the registered `table.atoms` together. +- `table.state` is Lit-only selected state. It is the value returned from the selector passed as the second argument to `tableController.table(...)`. + +The Lit adapter provides `litReactivity()` to the table's `coreReativityFeature`. Readonly and writable atoms are TanStack Store atoms. `TableController` subscribes to `table.store` and `table.optionsStore`; atom or options changes flowing through those stores call `host.requestUpdate()`. + +### Feature-based State + +State slices are only created for the features that are registered in `_features`. This keeps TanStack Table tree-shakeable and gives TypeScript more accurate state inference. + +```ts +const _features = tableFeatures({ + rowPaginationFeature, + rowSortingFeature, +}) + +const table = this.tableController.table({ + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, + columns, + data: this._data, +}) + +table.atoms.pagination.get() +table.atoms.sorting.get() + +// table.atoms.rowSelection // TypeScript error unless rowSelectionFeature is registered +``` + +If `_features` does not include a feature, its state should not be available in `table.atoms`, `table.store.state`, `table.state`, `initialState`, `state`, or `atoms`. ### Accessing Table State -You do not need to set up anything special in order for the table state to work. If you pass nothing into either `state`, `initialState`, or any of the `on[State]Change` table options, the table will manage its own state internally. You can access any part of this internal state by using the `table.getState()` table instance API. +There are two different questions when reading table state: + +- Do you only need the current value? +- Or should the Lit host update when that value changes? + +Use a direct atom or store read for the current value. Use `table.state` or `table.Subscribe` in render output when the host should reflect selected table state. + +#### Reading State Without Subscribing + +The simplest and most performant way to read a current state value is to read the matching atom: ```ts -private tableController = new TableController(this); +const pagination = table.atoms.pagination.get() +const sorting = table.atoms.sorting.get() +``` -render() { - const table = this.tableController.table({ - columns, - data, - ... - }) +You can also read the current flat store snapshot: - console.log(table.getState()) //access the entire internal state - console.log(table.getState().rowSelection) //access just the row selection state - // ... -} +```ts +const tableState = table.store.state +const pagination = table.store.state.pagination ``` -### Custom Initial State +These reads are current-value reads. The `TableController` handles host invalidation through its subscriptions to the table store and options store. If the UI needs to stay reactive to table state changes, use `table.state`, `table.Subscribe`, or a TanStack Store subscription. + +#### Reading Reactive State with TableController -If all you need to do for certain states is customize their initial default values, you still do not need to manage any of the state yourself. You can simply set values in the `initialState` option of the table instance. +The second argument to `tableController.table(...)` is a TanStack Store selector. The selected value is exposed as `table.state`. The default selector selects all registered table state. ```ts -render() { - const table = this.tableController.table({ - columns, - data, - initialState: { - columnOrder: ['age', 'firstName', 'lastName'], //customize the initial column order - columnVisibility: { - id: false //hide the id column by default - }, - expanded: true, //expand all rows by default - sorting: [ - { - id: 'age', - desc: true //sort by age in descending order by default - } - ] +const table = this.tableController.table( + { + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), }, - }) - - return html`...`; -} + columns, + data: this._data, + }, + (state) => ({ + pagination: state.pagination, + }), +) + +table.state.pagination ``` -> **Note**: Only specify each particular state in either `initialState` or `state`, but not both. If you pass in a particular state value to both `initialState` and `state`, the initialized state in `state` will take overwrite any corresponding value in `initialState`. +#### Selecting State with table.Subscribe -### Controlled State +Use `table.Subscribe` in templates to select a slice of table state while rendering. -If you need easy access to the table state in other areas of your application, TanStack Table makes it easy to control and manage any or all of the table state in your own state management system. You can do this by passing in your own state and state management functions to the `state` and `on[State]Change` table options. +```ts +${table.Subscribe({ + selector: (state) => ({ + pagination: state.pagination, + }), + children: ({ pagination }) => html` + Page ${pagination.pageIndex + 1} + `, +})} +``` -#### Individual Controlled State +`table.Subscribe` can also accept a `source`, but in the current Lit adapter host invalidation is wired through the full `table.store` subscription. Treat source mode as a render-time selection convenience, not a guarantee of source-only host invalidation. -You can control just the state that you need easy access to. You do NOT have to control all of the table state if you do not need to. It is recommended to only control the state that you need on a case-by-case basis. +### Setting Table State -In order to control a particular state, you need to both pass in the corresponding `state` value and the `on[State]Change` function to the table instance. +You should almost never need to set table state directly. TanStack Table features expose dedicated APIs for interacting with their state, and those APIs are the safest way to make changes. -Let's take filtering, sorting, and pagination as an example in a "manual" server-side data fetching scenario. You can store the filtering, sorting, and pagination state in your own state management, but leave out any other state like column order, column visibility, etc. if your API does not care about those values. +```ts +table.nextPage() +table.previousPage() +table.setPageIndex(0) +table.setPageSize(25) +``` -```jsx -import {html} from "lit"; +Use APIs like `table.setSorting(...)`, `table.setColumnFilters(...)`, `column.toggleVisibility()`, or `row.toggleSelected()` instead of manually editing the underlying state object. -@customElement('my-component') -class MyComponent extends LitElement { - @state() - private _sorting: SortingState = [] +If you only care about setting starting values, use `initialState`. If you want to reset a state slice back to its initial value, use that feature's reset API. - render() { - const table = this.tableController.table({ - columns, - data, - state: { - sorting: this._sorting, - }, - onSortingChange: updaterOrValue => { - if (typeof updaterOrValue === 'function') { - this._sorting = updaterOrValue(this._sorting) - } else { - this._sorting = updaterOrValue - } - }, - getSortedRowModel: getSortedRowModel(), - getCoreRowModel: getCoreRowModel(), - }) +If you really do need to write a state slice directly, the low-level write surface for internally owned state is the matching base atom: - return html`...` - } -} -//... +```ts +table.baseAtoms.pagination.set((old) => ({ + ...old, + pageIndex: 0, +})) ``` -#### Fully Controlled State +Direct base atom writes should be rare. If a slice is owned by an external atom passed through `atoms`, write to that external atom instead; `table.atoms.pagination` will read from the external atom, not the internal base atom. -Alternatively, you can control the entire table state with the `onStateChange` table option. It will hoist out the entire table state into your own state management system. Be careful with this approach, as you might find that raising some frequently changing state values up a component tree, like `columnSizingInfo` state`, might cause bad performance issues. +### Custom Initial State -A couple of more tricks may be needed to make this work. If you use the `onStateChange` table option, the initial values of the `state` must be populated with all of the relevant state values for all of the features that you want to use. You can either manually type out all of the initial state values, or use the `table.setOptions` API in a special way as shown below. +If you only need to customize the starting value for some table state, use `initialState`. You still do not need to manage that state yourself. + +`initialState` only applies to registered state slices. It is used to create the table's initial state and is also used by reset APIs such as `table.resetSorting()` or `table.resetPagination()`. Changing the `initialState` object later does not reset table state. ```ts +const table = this.tableController.table({ + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data: this._data, + initialState: { + sorting: [ + { + id: 'age', + desc: true, + }, + ], + pagination: { + pageIndex: 0, + pageSize: 25, + }, + }, +}) +``` -private tableController = new TableController(this); +> **Note:** Do not provide the same state slice in multiple ownership places unless you intentionally want one to win. For a slice like `pagination`, prefer exactly one of `initialState.pagination`, `atoms.pagination`, or `state.pagination` as the source of truth. External atoms take precedence over external `state`; external `state` syncs into the table's internal base atom. -@state() -private _tableState; +#### Resetting to Initial State -render() { - const table = this.tableController.table({ - columns, - data, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel() - }) - const state = { ...table.initialState, ...this._tableState }; - table.setOptions(prev => ({ - ...prev, - state, - onStateChange: updater => { - this._tableState = - updater instanceof Function ? updater(state) : updater //any state changes will be pushed up to our own state management - }, - })) +Feature reset APIs reset to `table.initialState` by default. Many reset APIs also accept `true` to reset to that feature's blank/default state instead: - return html`...`; -} +```ts +table.resetSorting() +table.resetPagination() +table.resetPagination(true) ``` -### On State Change Callbacks +Slice reset APIs like `resetPagination()` update through that feature's state updater and can update an externally owned atom. The core `table.reset()` API resets the internal base atoms, so do not use it as the primary way to reset state that is owned by external atoms. + +### Controlled State + +If you need easy access to table state in other parts of your application, you can control individual state slices. In v9, external atoms are the recommended way to do this when you want atomic ownership. + +#### External Atoms + +Use external atoms when the app should own one or more table state slices. Create stable writable atoms with `createAtom` from `@tanstack/store`, pass them to `atoms`, and read them where needed. + +```ts +import { createAtom } from '@tanstack/store' +import { + TableController, + rowPaginationFeature, + tableFeatures, + type PaginationState, +} from '@tanstack/lit-table' + +const _features = tableFeatures({ + rowPaginationFeature, +}) + +const paginationAtom = createAtom({ + pageIndex: 0, + pageSize: 10, +}) + +const table = this.tableController.table({ + _features, + _rowModels: {}, + columns, + data: this._data, + atoms: { + pagination: paginationAtom, + }, + manualPagination: true, +}) + +const pagination = paginationAtom.get() +``` -So far, we have seen the `on[State]Change` and `onStateChange` table options work to "hoist" the table state changes into our own state management. However, there are a few things about these using these options that you should be aware of. +When using the `atoms` option for a slice, you do not need to add the matching `on[State]Change` option. -#### 1. **State Change Callbacks MUST have their corresponding state value in the `state` option**. +#### External State -Specifying an `on[State]Change` callback tells the table instance that this will be a controlled state. If you do not specify the corresponding `state` value, that state will be "frozen" with its initial value. +The classic `state` plus `on[State]Change` pattern is still supported. This can be convenient for simple Lit `@state()` integrations or when migrating v8 code, but it is less atomic than external atoms. -```jsx +```ts @state() -private _sorting = []; -//... -render() { +private _sorting: SortingState = [] + +protected render() { const table = this.tableController.table({ + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + }, columns, - data, + data: this._data, state: { sorting: this._sorting, }, - onSortingChange: updaterOrValue => { - if (typeof updaterOrValue === 'function') { - this._sorting = updaterOrValue(this._sorting) - } else { - this._sorting = updaterOrValue - } + onSortingChange: (updater) => { + this._sorting = + updater instanceof Function ? updater(this._sorting) : updater }, - getSortedRowModel: getSortedRowModel(), - getCoreRowModel: getCoreRowModel(), }) - return html`...`; + return html`...` } ``` -#### 2. **Updaters can either be raw values or callback functions**. +The v8-style `onStateChange` option is no longer part of the v9 `TableController` state model. v9 encourages keeping table state slices atomic and separated for performance. -The `on[State]Change` and `onStateChange` callbacks work exactly like the `setState` functions in React. The updater values can either be a new state value or a callback function that takes the previous state value and returns the new state value. +##### On State Change Callbacks -What implications does this have? It means that if you want to add in some extra logic in any of the `on[State]Change` callbacks, you can do so, but you need to check whether or not the new incoming updater value is a function or value. +The `on[State]Change` callbacks are useful when you are controlling a matching slice through the `state` option. They receive either a raw value or an updater function. -This is why you will see the `updater instanceof Function ? updater(state.value) : updater` pattern in the examples above. This pattern checks if the updater is a function, and if it is, it calls the function with the previous state value to get the new state value. +If you provide an `on[State]Change` callback, also provide the corresponding value in `state`. For example, `onSortingChange` should be paired with `state.sorting`. + +```ts +onPaginationChange: (updater) => { + this._pagination = + updater instanceof Function ? updater(this._pagination) : updater +} +``` ### State Types -All complex states in TanStack Table have their own TypeScript types that you can import and use. This can be handy for ensuring that you are using the correct data structures and properties for the state values that you are controlling. +Most complex states in TanStack Table have their own TypeScript types that you can import and use. + +```ts +import { + TableController, + type PaginationState, + type RowSelectionState, + type SortingState, + type TableState, +} from '@tanstack/lit-table' -```tsx -import { TableController, type SortingState } from '@tanstack/lit-table' -//... @state() private _sorting: SortingState = [ { - id: 'age', //you should get autocomplete for the `id` and `desc` properties + id: 'age', desc: true, - } + }, ] ``` + +`TableState` is inferred from the features registered on that table: + +```ts +type MyTableState = TableState +``` diff --git a/docs/framework/lit/lit-table.md b/docs/framework/lit/lit-table.md index 8a5c263c9f..f2c8aa35d5 100644 --- a/docs/framework/lit/lit-table.md +++ b/docs/framework/lit/lit-table.md @@ -2,62 +2,164 @@ title: Lit Table --- -The `@tanstack/lit-table` adapter is a wrapper around the core table logic. Most of it's job is related to managing state the "lit" way, providing types and the rendering implementation of cell/header/footer templates. +The `@tanstack/lit-table` adapter wraps `@tanstack/table-core` with a Lit `ReactiveController`, rendering helpers, and types. `TableController` installs the Lit `coreReativityFeature` for you, so TanStack Store atom changes can request Lit host updates. -## Exports +TanStack Table v9 is explicit about what a table uses. Register features with `_features`, and register client-side row model factories with `_rowModels`. The core row model is included by default, so a basic table can use `_rowModels: {}`. -`@tanstack/lit-table` re-exports all of `@tanstack/table-core`'s APIs and the following: +## Creating a Table -### `TableController` - -Is a reactive controller that provides a `table` API that takes an `options` object and returns a table instance. +Create one `TableController` for the Lit host, then call `tableController.table(...)` during render. ```ts -import { TableController } from '@tanstack/lit-table' +import { LitElement, html } from 'lit' +import { customElement, state } from 'lit/decorators.js' +import { + TableController, + tableFeatures, + type ColumnDef, +} from '@tanstack/lit-table' + +type Person = { + firstName: string + lastName: string + age: number +} + +const _features = tableFeatures({}) + +const columns: Array> = [ + { + accessorKey: 'firstName', + header: 'First name', + cell: (info) => info.getValue(), + }, +] -@customElement('my-table-element') -class MyTableElement extends LitElement { - private tableController = new TableController(this) +@customElement('people-table') +export class PeopleTable extends LitElement { + private tableController = new TableController(this) + + @state() + private data: Person[] = [] protected render() { - const table = this.tableController.table(options) - // ...render your table + const table = this.tableController.table({ + _features, + _rowModels: {}, + columns, + data: this.data, + }) + + return html`...` } } ``` -### `flexRender` +For feature-specific row models, register the feature and put the row model factory under `_rowModels`. + +```ts +import { + createPaginatedRowModel, + createSortedRowModel, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/lit-table' + +const _features = tableFeatures({ + rowPaginationFeature, + rowSortingFeature, +}) + +const tableOptions = { + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, +} +``` + +## Table State + +Table state is managed with TanStack Store atoms in v9. For most tables, you do not need to manage table state yourself: set `initialState` when you need starting values, and use feature APIs like `table.nextPage()`, `table.setSorting(...)`, and `row.toggleSelected()` instead of mutating state directly. + +Use `atoms` when your app should own one state slice with TanStack Store. Lit `@state()` values can also be passed through `state` with the matching `on[State]Change` option for simple integrations. Selected table state is available on `table.state` when you pass a selector to `tableController.table(...)`. + +```ts +import { createAtom } from '@tanstack/store' +import { + TableController, + rowPaginationFeature, + tableFeatures, + type PaginationState, +} from '@tanstack/lit-table' -A utility function for rendering cell/header/footer templates with dynamic values. +const _features = tableFeatures({ + rowPaginationFeature, +}) -Example: +const paginationAtom = createAtom({ + pageIndex: 0, + pageSize: 10, +}) -```jsx -import { flexRender } from '@tanstack/lit-table' -//... +const table = this.tableController.table({ + _features, + _rowModels: {}, + columns, + data: this.data, + atoms: { + pagination: paginationAtom, + }, +}) +``` + +See the [Table State Guide](./guide/table-state.md) for selectors, external atoms, and state ownership patterns. + +## Rendering Headers, Cells, and Footers + +Use `table.FlexRender` to render column `header`, `cell`, and `footer` definitions. It handles plain values and Lit templates. + +```ts return html` - - ${table - .getRowModel() - .rows.slice(0, 10) - .map( - row => html` + + ${table.getRowModel().rows.map( + (row) => html` - ${row - .getVisibleCells() - .map( - cell => html` - - ${flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - - ` - )} + ${row.getVisibleCells().map( + (cell) => html`${table.FlexRender({ cell })}`, + )} - ` + `, )} - + ` ``` + +## createTableHook + +`createTableHook` creates app-specific Lit table helpers. Use it when multiple tables should share `_features`, `_rowModels`, default options, column helpers, and component conventions. + +```ts +import { createTableHook, tableFeatures } from '@tanstack/lit-table' + +const { useAppTable, createAppColumnHelper } = createTableHook({ + _features: tableFeatures({}), + _rowModels: {}, +}) + +const columnHelper = createAppColumnHelper() + +const table = useAppTable(this, { + columns, + data: this.data, +}) +``` + +See the [Composable Tables example](./examples/composable-tables) for the full pattern. + +## API Reference + +See the [Lit API Reference](./reference/index.md). diff --git a/docs/framework/lit/reference/classes/TableController.md b/docs/framework/lit/reference/classes/TableController.md new file mode 100644 index 0000000000..45af560b02 --- /dev/null +++ b/docs/framework/lit/reference/classes/TableController.md @@ -0,0 +1,175 @@ +--- +id: TableController +title: TableController +--- + +# Class: TableController\ + +Defined in: [TableController.ts:132](https://github.com/TanStack/table/blob/main/packages/lit-table/src/TableController.ts#L132) + +A Lit ReactiveController for TanStack Table integration. + +Uses `constructReactivityFeature` from table-core to properly integrate +with the TanStack Store reactivity system, matching the pattern used by +all other framework adapters (React, Vue, Solid, Svelte, Angular). + +## Example + +```ts +@customElement('my-table') +class MyTable extends LitElement { + private tableController = new TableController(this) + + protected render() { + const table = this.tableController.table( + { + _features, + _rowModels: {}, + columns, + data, + }, + (state) => ({ sorting: state.sorting }), + ) + // use table in your template... + } +} +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +## Implements + +- `ReactiveController` + +## Constructors + +### Constructor + +```ts +new TableController(host): TableController; +``` + +Defined in: [TableController.ts:143](https://github.com/TanStack/table/blob/main/packages/lit-table/src/TableController.ts#L143) + +#### Parameters + +##### host + +`ReactiveControllerHost` + +#### Returns + +`TableController`\<`TFeatures`, `TData`\> + +## Properties + +### host + +```ts +host: ReactiveControllerHost; +``` + +Defined in: [TableController.ts:136](https://github.com/TanStack/table/blob/main/packages/lit-table/src/TableController.ts#L136) + +## Methods + +### hostConnected() + +```ts +hostConnected(): void; +``` + +Defined in: [TableController.ts:244](https://github.com/TanStack/table/blob/main/packages/lit-table/src/TableController.ts#L244) + +Called when the host is connected to the component tree. For custom +element hosts, this corresponds to the `connectedCallback()` lifecycle, +which is only called when the component is connected to the document. + +#### Returns + +`void` + +#### Implementation of + +```ts +ReactiveController.hostConnected +``` + +*** + +### hostDisconnected() + +```ts +hostDisconnected(): void; +``` + +Defined in: [TableController.ts:248](https://github.com/TanStack/table/blob/main/packages/lit-table/src/TableController.ts#L248) + +Called when the host is disconnected from the component tree. For custom +element hosts, this corresponds to the `disconnectedCallback()` lifecycle, +which is called the host or an ancestor component is disconnected from the +document. + +#### Returns + +`void` + +#### Implementation of + +```ts +ReactiveController.hostDisconnected +``` + +*** + +### table() + +```ts +table(tableOptions, selector?): LitTable; +``` + +Defined in: [TableController.ts:163](https://github.com/TanStack/table/blob/main/packages/lit-table/src/TableController.ts#L163) + +Returns the Lit-backed table instance for the current render pass. + +The first call constructs the table with Lit reactivity bindings and +subscribes the host to table state/options changes. Later calls merge new +options into the same table instance and expose selected state through +`table.state`. + +#### Type Parameters + +##### TSelected + +`TSelected` = `TableState`\<`TFeatures`\> + +#### Parameters + +##### tableOptions + +`TableOptions`\<`TFeatures`, `TData`\> + +##### selector? + +(`state`) => `TSelected` + +#### Returns + +[`LitTable`](../type-aliases/LitTable.md)\<`TFeatures`, `TData`, `TSelected`\> + +#### Example + +```ts +const table = this.tableController.table( + { _features, _rowModels: {}, columns, data }, + (state) => ({ sorting: state.sorting }), +) +``` diff --git a/docs/framework/lit/reference/functions/FlexRender-1.md b/docs/framework/lit/reference/functions/FlexRender-1.md new file mode 100644 index 0000000000..77a681d381 --- /dev/null +++ b/docs/framework/lit/reference/functions/FlexRender-1.md @@ -0,0 +1,54 @@ +--- +id: FlexRender +title: FlexRender +--- + +# Function: FlexRender() + +```ts +function FlexRender(props): string | TemplateResult | null; +``` + +Defined in: [flexRender.ts:90](https://github.com/TanStack/table/blob/main/packages/lit-table/src/flexRender.ts#L90) + +Simplified component wrapper of `flexRender`. Use this utility function to render headers, cells, or footers with custom markup. +Only one prop (`cell`, `header`, or `footer`) may be passed. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### props + +[`FlexRenderProps`](../type-aliases/FlexRenderProps.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`string` \| `TemplateResult` \| `null` + +## Example + +```ts +${FlexRender({ cell })} +${FlexRender({ header })} +${FlexRender({ footer: header })} +``` + +This replaces calling `flexRender` directly like this: +```ts +flexRender(cell.column.columnDef.cell, cell.getContext()) +flexRender(header.column.columnDef.header, header.getContext()) +flexRender(footer.column.columnDef.footer, footer.getContext()) +``` diff --git a/docs/framework/lit/reference/functions/createTableHook.md b/docs/framework/lit/reference/functions/createTableHook.md new file mode 100644 index 0000000000..421540a759 --- /dev/null +++ b/docs/framework/lit/reference/functions/createTableHook.md @@ -0,0 +1,355 @@ +--- +id: createTableHook +title: createTableHook +--- + +# Function: createTableHook() + +```ts +function createTableHook(__namedParameters): object; +``` + +Defined in: [createTableHook.ts:427](https://github.com/TanStack/table/blob/main/packages/lit-table/src/createTableHook.ts#L427) + +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 `@lit/context` in those components +- Get a `useAppTable` hook that returns an extended table with App wrapper functions +- Get a `createAppColumnHelper` function pre-bound to your features + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TTableComponents + +`TTableComponents` *extends* `Record`\<`string`, [`ComponentType`](../type-aliases/ComponentType.md)\<`any`\>\> + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](../type-aliases/ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](../type-aliases/ComponentType.md)\<`any`\>\> + +## Parameters + +### \_\_namedParameters + +[`CreateTableHookOptions`](../type-aliases/CreateTableHookOptions.md)\<`TFeatures`, `TTableComponents`, `TCellComponents`, `THeaderComponents`\> + +## Returns + +### appFeatures + +```ts +appFeatures: TFeatures; +``` + +### createAppColumnHelper() + +```ts +createAppColumnHelper: () => AppColumnHelper; +``` + +Create a column helper pre-bound to the features and components configured in this table hook. +The cell, header, and footer contexts include pre-bound components (e.g., `cell.TextCell`). + +#### Type Parameters + +##### TData + +`TData` *extends* `RowData` + +#### Returns + +[`AppColumnHelper`](../type-aliases/AppColumnHelper.md)\<`TFeatures`, `TData`, `TCellComponents`, `THeaderComponents`\> + +#### Example + +```ts +const columnHelper = createAppColumnHelper() + +const columns = [ + columnHelper.accessor('firstName', { + header: 'First Name', + cell: ({ cell }) => cell.FlexRender(), // cell has pre-bound components! + }), + columnHelper.accessor('age', { + header: 'Age', + cell: ({ cell }) => cell.NumberCell(), + }), +] +``` + +### useAppTable() + +```ts +useAppTable: (host, tableOptions, selector?) => object; +``` + +Enhanced table hook that returns a controller-like object with a `table()` method. +The returned table has App wrapper functions and pre-bound tableComponents +attached directly to the table object. + +Default options from createTableHook are automatically merged with +the options passed here. Options passed here take precedence. + +TFeatures is already known from the createTableHook call; TData is inferred from the data prop. + +#### Type Parameters + +##### TData + +`TData` *extends* `RowData` + +##### TSelected + +`TSelected` = `TableState`\<`TFeatures`\> + +#### Parameters + +##### host + +`ReactiveControllerHost` & `HTMLElement` + +##### tableOptions + +`Omit`\<`TableOptions`\<`TFeatures`, `TData`\>, `"_features"` \| `"_rowModels"`\> + +##### selector? + +(`state`) => `TSelected` + +#### Returns + +`object` + +##### table() + +```ts +table: () => AppLitTable; +``` + +###### Returns + +[`AppLitTable`](../type-aliases/AppLitTable.md)\<`TFeatures`, `TData`, `TSelected`, `TTableComponents`, `TCellComponents`, `THeaderComponents`\> + +#### Example + +```ts +@customElement('my-table') +class MyTable extends LitElement { + private appTable = useAppTable(this, { + columns, + data: this.data, + }) + + protected render() { + const table = this.appTable.table() + return html`...` + } +} +``` + +### useCellContext() + +```ts +useCellContext: (host) => ContextConsumer>, ReactiveControllerHost & HTMLElement>; +``` + +Access the cell instance from within a custom element that is a descendant +of an element providing cell context. +Uses `@lit/context` ContextConsumer to retrieve the cell. +TFeatures is already known from the createTableHook call. + +#### Type Parameters + +##### TValue + +`TValue` *extends* `unknown` = `unknown` + +#### Parameters + +##### host + +`ReactiveControllerHost` & `HTMLElement` + +#### Returns + +`ContextConsumer`\<`Context`\<`symbol`, `Cell`\<`TFeatures`, `any`, `TValue`\>\>, `ReactiveControllerHost` & `HTMLElement`\> + +#### Example + +```ts +@customElement('text-cell') +class TextCell extends LitElement { + private _cell = useCellContext(this) + + protected render() { + const cell = this._cell.value + if (!cell) return html`` + return html`${cell.getValue()}` + } +} +``` + +### useHeaderContext() + +```ts +useHeaderContext: (host) => ContextConsumer>, ReactiveControllerHost & HTMLElement>; +``` + +Access the header instance from within a custom element that is a descendant +of an element providing header context. +Uses `@lit/context` ContextConsumer to retrieve the header. +TFeatures is already known from the createTableHook call. + +#### Type Parameters + +##### TValue + +`TValue` *extends* `unknown` = `unknown` + +#### Parameters + +##### host + +`ReactiveControllerHost` & `HTMLElement` + +#### Returns + +`ContextConsumer`\<`Context`\<`symbol`, `Header`\<`TFeatures`, `any`, `TValue`\>\>, `ReactiveControllerHost` & `HTMLElement`\> + +#### Example + +```ts +@customElement('sort-indicator') +class SortIndicator extends LitElement { + private _header = useHeaderContext(this) + + protected render() { + const header = this._header.value + if (!header) return html`` + const sorted = header.column.getIsSorted() + return html`${sorted === 'asc' ? '🔼' : sorted === 'desc' ? '🔽' : ''}` + } +} +``` + +### useTableContext() + +```ts +useTableContext: (host) => ContextConsumer>, ReactiveControllerHost & HTMLElement>; +``` + +Access the table instance from within a custom element that is a descendant +of the element using `useAppTable`. +Uses `@lit/context` ContextConsumer to retrieve the table from the nearest ancestor provider. +TFeatures is already known from the createTableHook call. + +#### Type Parameters + +##### TData + +`TData` *extends* `RowData` = `RowData` + +#### Parameters + +##### host + +`ReactiveControllerHost` & `HTMLElement` + +#### Returns + +`ContextConsumer`\<`Context`\<`symbol`, [`LitTable`](../type-aliases/LitTable.md)\<`TFeatures`, `TData`, `any`\>\>, `ReactiveControllerHost` & `HTMLElement`\> + +#### Example + +```ts +@customElement('pagination-controls') +class PaginationControls extends LitElement { + private _table = useTableContext(this) + + protected render() { + const table = this._table.value + if (!table) return html`` + return html` + + + ` + } +} +``` + +## Example + +```ts +// hooks/table.ts +export const { + createAppColumnHelper, + useAppTable, + useTableContext, + useCellContext, + useHeaderContext, +} = createTableHook({ + _features: tableFeatures({ + rowPaginationFeature, + rowSortingFeature, + columnFilteringFeature, + }), + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + filteredRowModel: createFilteredRowModel(filterFns), + }, + tableComponents: { PaginationControls, RowCount }, + cellComponents: { TextCell, NumberCell }, + headerComponents: { SortIndicator, ColumnFilter }, +}) + +// Create column helper with TFeatures already bound +const columnHelper = createAppColumnHelper() + +// my-table.ts +@customElement('my-table') +class MyTable extends LitElement { + private appTable = useAppTable(this, { + columns, + data: this.data, + }) + + protected render() { + const table = this.appTable.table() + + return html` + + + ${repeat(table.getHeaderGroups(), (hg) => hg.id, (hg) => html` + + ${hg.headers.map((h) => table.AppHeader(h, (header) => html` + + `))} + + `)} + + + ${table.getRowModel().rows.map((row) => html` + + ${row.getAllCells().map((c) => table.AppCell(c, (cell) => html` + + `))} + + `)} + +
${header.FlexRender()}
${cell.FlexRender()}
+ ` + } +} +``` diff --git a/docs/framework/lit/reference/functions/flexRender.md b/docs/framework/lit/reference/functions/flexRender.md new file mode 100644 index 0000000000..335f6180d3 --- /dev/null +++ b/docs/framework/lit/reference/functions/flexRender.md @@ -0,0 +1,44 @@ +--- +id: flexRender +title: flexRender +--- + +# Function: flexRender() + +```ts +function flexRender(Comp, props): string | TemplateResult | null; +``` + +Defined in: [flexRender.ts:22](https://github.com/TanStack/table/blob/main/packages/lit-table/src/flexRender.ts#L22) + +Renders a Lit table template value with the provided context props. + +Use this lower-level helper for custom header, cell, or footer renderers when +you already have the render function and context. `FlexRender` is the +convenience wrapper for table cell/header/footer objects. + +## Type Parameters + +### TProps + +`TProps` + +## Parameters + +### Comp + +`string` | `TemplateResult` | (`props`) => `string` \| `TemplateResult` | `undefined` + +### props + +`TProps` + +## Returns + +`string` \| `TemplateResult` \| `null` + +## Example + +```ts +flexRender(cell.column.columnDef.cell, cell.getContext()) +``` diff --git a/docs/framework/lit/reference/index.md b/docs/framework/lit/reference/index.md new file mode 100644 index 0000000000..4ad9f64782 --- /dev/null +++ b/docs/framework/lit/reference/index.md @@ -0,0 +1,32 @@ +--- +id: "@tanstack/lit-table" +title: "@tanstack/lit-table" +--- + +# @tanstack/lit-table + +## Classes + +- [TableController](classes/TableController.md) + +## Type Aliases + +- [AppCellContext](type-aliases/AppCellContext.md) +- [AppColumnDefBase](type-aliases/AppColumnDefBase.md) +- [AppColumnDefTemplate](type-aliases/AppColumnDefTemplate.md) +- [AppColumnHelper](type-aliases/AppColumnHelper.md) +- [AppDisplayColumnDef](type-aliases/AppDisplayColumnDef.md) +- [AppGroupColumnDef](type-aliases/AppGroupColumnDef.md) +- [AppHeaderContext](type-aliases/AppHeaderContext.md) +- [AppLitTable](type-aliases/AppLitTable.md) +- [ComponentType](type-aliases/ComponentType.md) +- [CreateTableHookOptions](type-aliases/CreateTableHookOptions.md) +- [FlexRenderProps](type-aliases/FlexRenderProps.md) +- [LitTable](type-aliases/LitTable.md) +- [SubscribeSource](type-aliases/SubscribeSource.md) + +## Functions + +- [createTableHook](functions/createTableHook.md) +- [flexRender](functions/flexRender.md) +- [FlexRender](functions/FlexRender-1.md) diff --git a/docs/framework/lit/reference/type-aliases/AppCellContext.md b/docs/framework/lit/reference/type-aliases/AppCellContext.md new file mode 100644 index 0000000000..1fbdd846bc --- /dev/null +++ b/docs/framework/lit/reference/type-aliases/AppCellContext.md @@ -0,0 +1,105 @@ +--- +id: AppCellContext +title: AppCellContext +--- + +# Type Alias: AppCellContext\ + +```ts +type AppCellContext = object; +``` + +Defined in: [createTableHook.ts:42](https://github.com/TanStack/table/blob/main/packages/lit-table/src/createTableHook.ts#L42) + +Enhanced CellContext with pre-bound cell components. +The `cell` property includes the registered cellComponents. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +## Properties + +### cell + +```ts +cell: Cell & TCellComponents & object; +``` + +Defined in: [createTableHook.ts:48](https://github.com/TanStack/table/blob/main/packages/lit-table/src/createTableHook.ts#L48) + +#### Type Declaration + +##### FlexRender() + +```ts +FlexRender: () => TemplateResult | string | null; +``` + +###### Returns + +`TemplateResult` \| `string` \| `null` + +*** + +### column + +```ts +column: Column; +``` + +Defined in: [createTableHook.ts:52](https://github.com/TanStack/table/blob/main/packages/lit-table/src/createTableHook.ts#L52) + +*** + +### getValue + +```ts +getValue: CellContext["getValue"]; +``` + +Defined in: [createTableHook.ts:53](https://github.com/TanStack/table/blob/main/packages/lit-table/src/createTableHook.ts#L53) + +*** + +### renderValue + +```ts +renderValue: CellContext["renderValue"]; +``` + +Defined in: [createTableHook.ts:54](https://github.com/TanStack/table/blob/main/packages/lit-table/src/createTableHook.ts#L54) + +*** + +### row + +```ts +row: Row; +``` + +Defined in: [createTableHook.ts:55](https://github.com/TanStack/table/blob/main/packages/lit-table/src/createTableHook.ts#L55) + +*** + +### table + +```ts +table: Table; +``` + +Defined in: [createTableHook.ts:56](https://github.com/TanStack/table/blob/main/packages/lit-table/src/createTableHook.ts#L56) diff --git a/docs/framework/lit/reference/type-aliases/AppColumnDefBase.md b/docs/framework/lit/reference/type-aliases/AppColumnDefBase.md new file mode 100644 index 0000000000..3d1b211955 --- /dev/null +++ b/docs/framework/lit/reference/type-aliases/AppColumnDefBase.md @@ -0,0 +1,56 @@ +--- +id: AppColumnDefBase +title: AppColumnDefBase +--- + +# Type Alias: AppColumnDefBase\ + +```ts +type AppColumnDefBase = Omit, "cell" | "header" | "footer"> & object; +``` + +Defined in: [createTableHook.ts:91](https://github.com/TanStack/table/blob/main/packages/lit-table/src/createTableHook.ts#L91) + +Enhanced column definition base with pre-bound components in cell/header/footer contexts. + +## Type Declaration + +### cell? + +```ts +optional cell: AppColumnDefTemplate>; +``` + +### footer? + +```ts +optional footer: AppColumnDefTemplate>; +``` + +### header? + +```ts +optional header: AppColumnDefTemplate>; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> diff --git a/docs/framework/lit/reference/type-aliases/AppColumnDefTemplate.md b/docs/framework/lit/reference/type-aliases/AppColumnDefTemplate.md new file mode 100644 index 0000000000..a412bd39e7 --- /dev/null +++ b/docs/framework/lit/reference/type-aliases/AppColumnDefTemplate.md @@ -0,0 +1,20 @@ +--- +id: AppColumnDefTemplate +title: AppColumnDefTemplate +--- + +# Type Alias: AppColumnDefTemplate\ + +```ts +type AppColumnDefTemplate = string | (props) => any; +``` + +Defined in: [createTableHook.ts:84](https://github.com/TanStack/table/blob/main/packages/lit-table/src/createTableHook.ts#L84) + +Template type for column definitions that can be a string or a function. + +## Type Parameters + +### TProps + +`TProps` *extends* `object` diff --git a/docs/framework/lit/reference/type-aliases/AppColumnHelper.md b/docs/framework/lit/reference/type-aliases/AppColumnHelper.md new file mode 100644 index 0000000000..60f2b28a92 --- /dev/null +++ b/docs/framework/lit/reference/type-aliases/AppColumnHelper.md @@ -0,0 +1,144 @@ +--- +id: AppColumnHelper +title: AppColumnHelper +--- + +# Type Alias: AppColumnHelper\ + +```ts +type AppColumnHelper = object; +``` + +Defined in: [createTableHook.ts:167](https://github.com/TanStack/table/blob/main/packages/lit-table/src/createTableHook.ts#L167) + +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 Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +## Properties + +### accessor() + +```ts +accessor: (accessor, column) => TAccessor extends AccessorFn ? AccessorFnColumnDef : AccessorKeyColumnDef; +``` + +Defined in: [createTableHook.ts:177](https://github.com/TanStack/table/blob/main/packages/lit-table/src/createTableHook.ts#L177) + +Creates a data column definition with an accessor key or function. +The cell, header, and footer contexts include pre-bound components. + +#### Type Parameters + +##### TAccessor + +`TAccessor` *extends* `AccessorFn`\<`TData`\> \| `DeepKeys`\<`TData`\> + +##### TValue + +`TValue` *extends* `TAccessor` *extends* `AccessorFn`\<`TData`, infer TReturn\> ? `TReturn` : `TAccessor` *extends* `DeepKeys`\<`TData`\> ? `DeepValue`\<`TData`, `TAccessor`\> : `never` + +#### Parameters + +##### accessor + +`TAccessor` + +##### column + +`TAccessor` *extends* `AccessorFn`\<`TData`\> ? [`AppColumnDefBase`](AppColumnDefBase.md)\<`TFeatures`, `TData`, `TValue`, `TCellComponents`, `THeaderComponents`\> & `object` : [`AppColumnDefBase`](AppColumnDefBase.md)\<`TFeatures`, `TData`, `TValue`, `TCellComponents`, `THeaderComponents`\> + +#### Returns + +`TAccessor` *extends* `AccessorFn`\<`TData`\> ? `AccessorFnColumnDef`\<`TFeatures`, `TData`, `TValue`\> : `AccessorKeyColumnDef`\<`TFeatures`, `TData`, `TValue`\> + +*** + +### columns() + +```ts +columns: (columns) => ColumnDef[] & [...TColumns]; +``` + +Defined in: [createTableHook.ts:208](https://github.com/TanStack/table/blob/main/packages/lit-table/src/createTableHook.ts#L208) + +Wraps an array of column definitions to preserve each column's individual TValue type. + +#### Type Parameters + +##### TColumns + +`TColumns` *extends* `ReadonlyArray`\<`ColumnDef`\<`TFeatures`, `TData`, `any`\>\> + +#### Parameters + +##### columns + +\[`...TColumns`\] + +#### Returns + +`ColumnDef`\<`TFeatures`, `TData`, `any`\>[] & \[`...TColumns`\] + +*** + +### display() + +```ts +display: (column) => DisplayColumnDef; +``` + +Defined in: [createTableHook.ts:216](https://github.com/TanStack/table/blob/main/packages/lit-table/src/createTableHook.ts#L216) + +Creates a display column definition for non-data columns. +The cell, header, and footer contexts include pre-bound components. + +#### Parameters + +##### column + +[`AppDisplayColumnDef`](AppDisplayColumnDef.md)\<`TFeatures`, `TData`, `TCellComponents`, `THeaderComponents`\> + +#### Returns + +`DisplayColumnDef`\<`TFeatures`, `TData`, `unknown`\> + +*** + +### group() + +```ts +group: (column) => GroupColumnDef; +``` + +Defined in: [createTableHook.ts:229](https://github.com/TanStack/table/blob/main/packages/lit-table/src/createTableHook.ts#L229) + +Creates a group column definition with nested child columns. +The cell, header, and footer contexts include pre-bound components. + +#### Parameters + +##### column + +[`AppGroupColumnDef`](AppGroupColumnDef.md)\<`TFeatures`, `TData`, `TCellComponents`, `THeaderComponents`\> + +#### Returns + +`GroupColumnDef`\<`TFeatures`, `TData`, `unknown`\> diff --git a/docs/framework/lit/reference/type-aliases/AppDisplayColumnDef.md b/docs/framework/lit/reference/type-aliases/AppDisplayColumnDef.md new file mode 100644 index 0000000000..63cf03144f --- /dev/null +++ b/docs/framework/lit/reference/type-aliases/AppDisplayColumnDef.md @@ -0,0 +1,52 @@ +--- +id: AppDisplayColumnDef +title: AppDisplayColumnDef +--- + +# Type Alias: AppDisplayColumnDef\ + +```ts +type AppDisplayColumnDef = Omit, "cell" | "header" | "footer"> & object; +``` + +Defined in: [createTableHook.ts:115](https://github.com/TanStack/table/blob/main/packages/lit-table/src/createTableHook.ts#L115) + +Enhanced display column definition with pre-bound components. + +## Type Declaration + +### cell? + +```ts +optional cell: AppColumnDefTemplate>; +``` + +### footer? + +```ts +optional footer: AppColumnDefTemplate>; +``` + +### header? + +```ts +optional header: AppColumnDefTemplate>; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> diff --git a/docs/framework/lit/reference/type-aliases/AppGroupColumnDef.md b/docs/framework/lit/reference/type-aliases/AppGroupColumnDef.md new file mode 100644 index 0000000000..3d58babc0b --- /dev/null +++ b/docs/framework/lit/reference/type-aliases/AppGroupColumnDef.md @@ -0,0 +1,58 @@ +--- +id: AppGroupColumnDef +title: AppGroupColumnDef +--- + +# Type Alias: AppGroupColumnDef\ + +```ts +type AppGroupColumnDef = Omit, "cell" | "header" | "footer" | "columns"> & object; +``` + +Defined in: [createTableHook.ts:138](https://github.com/TanStack/table/blob/main/packages/lit-table/src/createTableHook.ts#L138) + +Enhanced group column definition with pre-bound components. + +## Type Declaration + +### cell? + +```ts +optional cell: AppColumnDefTemplate>; +``` + +### columns? + +```ts +optional columns: ColumnDef[]; +``` + +### footer? + +```ts +optional footer: AppColumnDefTemplate>; +``` + +### header? + +```ts +optional header: AppColumnDefTemplate>; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> diff --git a/docs/framework/lit/reference/type-aliases/AppHeaderContext.md b/docs/framework/lit/reference/type-aliases/AppHeaderContext.md new file mode 100644 index 0000000000..3f5cd3718d --- /dev/null +++ b/docs/framework/lit/reference/type-aliases/AppHeaderContext.md @@ -0,0 +1,75 @@ +--- +id: AppHeaderContext +title: AppHeaderContext +--- + +# Type Alias: AppHeaderContext\ + +```ts +type AppHeaderContext = object; +``` + +Defined in: [createTableHook.ts:63](https://github.com/TanStack/table/blob/main/packages/lit-table/src/createTableHook.ts#L63) + +Enhanced HeaderContext with pre-bound header components. +The `header` property includes the registered headerComponents. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +## Properties + +### column + +```ts +column: Column; +``` + +Defined in: [createTableHook.ts:69](https://github.com/TanStack/table/blob/main/packages/lit-table/src/createTableHook.ts#L69) + +*** + +### header + +```ts +header: Header & THeaderComponents & object; +``` + +Defined in: [createTableHook.ts:70](https://github.com/TanStack/table/blob/main/packages/lit-table/src/createTableHook.ts#L70) + +#### Type Declaration + +##### FlexRender() + +```ts +FlexRender: () => TemplateResult | string | null; +``` + +###### Returns + +`TemplateResult` \| `string` \| `null` + +*** + +### table + +```ts +table: Table; +``` + +Defined in: [createTableHook.ts:74](https://github.com/TanStack/table/blob/main/packages/lit-table/src/createTableHook.ts#L74) diff --git a/docs/framework/lit/reference/type-aliases/AppLitTable.md b/docs/framework/lit/reference/type-aliases/AppLitTable.md new file mode 100644 index 0000000000..c06233f64c --- /dev/null +++ b/docs/framework/lit/reference/type-aliases/AppLitTable.md @@ -0,0 +1,161 @@ +--- +id: AppLitTable +title: AppLitTable +--- + +# Type Alias: AppLitTable\ + +```ts +type AppLitTable = LitTable & NoInfer & object; +``` + +Defined in: [createTableHook.ts:282](https://github.com/TanStack/table/blob/main/packages/lit-table/src/createTableHook.ts#L282) + +Extended table API returned by useAppTable with all App wrapper functions + +## Type Declaration + +### AppCell() + +```ts +AppCell: (cell, renderFn) => TemplateResult | string; +``` + +Wraps a cell and provides cell context with pre-bound cellComponents. + +#### Type Parameters + +##### TValue + +`TValue` *extends* `CellData` = `CellData` + +#### Parameters + +##### cell + +`Cell`\<`TFeatures`, `TData`, `TValue`\> + +##### renderFn + +(`cell`) => `TemplateResult` \| `string` + +#### Returns + +`TemplateResult` \| `string` + +#### Example + +```ts +${table.AppCell(cell, (c) => html`${c.FlexRender()}`)} +``` + +### AppFooter() + +```ts +AppFooter: (header, renderFn) => TemplateResult | string; +``` + +Wraps a footer and provides header context with pre-bound headerComponents. + +#### Type Parameters + +##### TValue + +`TValue` *extends* `CellData` = `CellData` + +#### Parameters + +##### header + +`Header`\<`TFeatures`, `TData`, `TValue`\> + +##### renderFn + +(`header`) => `TemplateResult` \| `string` + +#### Returns + +`TemplateResult` \| `string` + +#### Example + +```ts +${table.AppFooter(footer, (f) => html`${f.FlexRender()}`)} +``` + +### AppHeader() + +```ts +AppHeader: (header, renderFn) => TemplateResult | string; +``` + +Wraps a header and provides header context with pre-bound headerComponents. + +#### Type Parameters + +##### TValue + +`TValue` *extends* `CellData` = `CellData` + +#### Parameters + +##### header + +`Header`\<`TFeatures`, `TData`, `TValue`\> + +##### renderFn + +(`header`) => `TemplateResult` \| `string` + +#### Returns + +`TemplateResult` \| `string` + +#### Example + +```ts +${table.AppHeader(header, (h) => html`${h.FlexRender()}`)} +``` + +### FlexRender + +```ts +FlexRender: typeof FlexRender; +``` + +Convenience FlexRender function attached to the table instance. +Renders cell, header, or footer content from column definitions. + +#### Example + +```ts +${table.FlexRender({ header })} +${table.FlexRender({ cell })} +${table.FlexRender({ footer: header })} +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TSelected + +`TSelected` + +### TTableComponents + +`TTableComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> diff --git a/docs/framework/lit/reference/type-aliases/ComponentType.md b/docs/framework/lit/reference/type-aliases/ComponentType.md new file mode 100644 index 0000000000..dc7a43145a --- /dev/null +++ b/docs/framework/lit/reference/type-aliases/ComponentType.md @@ -0,0 +1,28 @@ +--- +id: ComponentType +title: ComponentType +--- + +# Type Alias: ComponentType()\ + +```ts +type ComponentType = (props) => any; +``` + +Defined in: [createTableHook.ts:32](https://github.com/TanStack/table/blob/main/packages/lit-table/src/createTableHook.ts#L32) + +## Type Parameters + +### T + +`T` *extends* `Record`\<`string`, `any`\> + +## Parameters + +### props + +`T` + +## Returns + +`any` diff --git a/docs/framework/lit/reference/type-aliases/CreateTableHookOptions.md b/docs/framework/lit/reference/type-aliases/CreateTableHookOptions.md new file mode 100644 index 0000000000..03203bdc4e --- /dev/null +++ b/docs/framework/lit/reference/type-aliases/CreateTableHookOptions.md @@ -0,0 +1,83 @@ +--- +id: CreateTableHookOptions +title: CreateTableHookOptions +--- + +# Type Alias: CreateTableHookOptions\ + +```ts +type CreateTableHookOptions = Omit, "columns" | "data" | "store" | "state" | "initialState"> & object; +``` + +Defined in: [createTableHook.ts:247](https://github.com/TanStack/table/blob/main/packages/lit-table/src/createTableHook.ts#L247) + +Options for creating a table hook with pre-bound components and default table options. +Extends all TableOptions except 'columns' | 'data' | 'store' | 'state' | 'initialState'. + +## Type Declaration + +### cellComponents? + +```ts +optional cellComponents: TCellComponents; +``` + +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 + +```ts +{ TextCell, NumberCell, DateCell, CurrencyCell } +``` + +### headerComponents? + +```ts +optional headerComponents: THeaderComponents; +``` + +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 + +```ts +{ SortIndicator, ColumnFilter, ResizeHandle } +``` + +### tableComponents? + +```ts +optional tableComponents: TTableComponents; +``` + +Table-level components that need access to the table instance. +These are available directly on the table object returned by useAppTable. +Use `useTableContext()` inside these components. + +#### Example + +```ts +{ PaginationControls, GlobalFilter, RowCount } +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TTableComponents + +`TTableComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> diff --git a/docs/framework/lit/reference/type-aliases/FlexRenderProps.md b/docs/framework/lit/reference/type-aliases/FlexRenderProps.md new file mode 100644 index 0000000000..870f7f0699 --- /dev/null +++ b/docs/framework/lit/reference/type-aliases/FlexRenderProps.md @@ -0,0 +1,59 @@ +--- +id: FlexRenderProps +title: FlexRenderProps +--- + +# Type Alias: FlexRenderProps\ + +```ts +type FlexRenderProps = + | { + cell: Cell; + footer?: never; + header?: never; +} + | { + cell?: never; + footer?: never; + header: Header; +} + | { + cell?: never; + footer: Header; + header?: never; +}; +``` + +Defined in: [flexRender.ts:56](https://github.com/TanStack/table/blob/main/packages/lit-table/src/flexRender.ts#L56) + +Simplified component wrapper of `flexRender`. Use this utility function to render headers, cells, or footers with custom markup. +Only one prop (`cell`, `header`, or `footer`) may be passed. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` = `CellData` + +## Example + +```ts +${FlexRender({ cell })} +${FlexRender({ header })} +${FlexRender({ footer: header })} +``` + +This replaces calling `flexRender` directly like this: +```ts +flexRender(cell.column.columnDef.cell, cell.getContext()) +flexRender(header.column.columnDef.header, header.getContext()) +flexRender(footer.column.columnDef.footer, footer.getContext()) +``` diff --git a/docs/framework/lit/reference/type-aliases/LitTable.md b/docs/framework/lit/reference/type-aliases/LitTable.md new file mode 100644 index 0000000000..6aa35c54e7 --- /dev/null +++ b/docs/framework/lit/reference/type-aliases/LitTable.md @@ -0,0 +1,190 @@ +--- +id: LitTable +title: LitTable +--- + +# Type Alias: LitTable\ + +```ts +type LitTable = Table & object; +``` + +Defined in: [TableController.ts:30](https://github.com/TanStack/table/blob/main/packages/lit-table/src/TableController.ts#L30) + +The extended table type returned by the Lit adapter. +Includes a `Subscribe` method for fine-grained state subscriptions +and a `state` property with the selected state. + +## Type Declaration + +### FlexRender + +```ts +FlexRender: typeof FlexRender; +``` + +Convenience FlexRender function attached to the table instance. +Renders cell, header, or footer content from column definitions. + +#### Example + +```ts +${table.FlexRender({ header })} +${table.FlexRender({ cell })} +${table.FlexRender({ footer: header })} +``` + +### state + +```ts +readonly state: Readonly; +``` + +The selected state of the table. This state may not match the structure of +`table.store.state` because it is selected by the `selector` function that +you pass as the 2nd argument to `controller.table()`. + +#### Example + +```ts +const table = this.tableController.table(options, (state) => ({ + globalFilter: state.globalFilter, +})) + +console.log(table.state.globalFilter) +``` + +### Subscribe() + +```ts +Subscribe: { + (props): string | TemplateResult; + (props): string | TemplateResult; + (props): string | TemplateResult; +}; +``` + +Subscribe to a selected slice of table state, or to a single source (atom or store). + +**Lit note:** `TableController` still wires host updates via the full `table.store` +subscription — source mode matches the React API and reads `source.get()` at render +time. True source-only invalidation can be added later via `source.subscribe`. + +#### Call Signature + +```ts +(props): string | TemplateResult; +``` + +##### Type Parameters + +###### TSourceValue + +`TSourceValue` + +##### Parameters + +###### props + +###### children + +(`state`) => `TemplateResult` \| `string` \| `TemplateResult` \| `string` + +###### selector? + +`undefined` + +###### source + +[`SubscribeSource`](SubscribeSource.md)\<`TSourceValue`\> + +##### Returns + +`string` \| `TemplateResult` + +#### Call Signature + +```ts +(props): string | TemplateResult; +``` + +##### Type Parameters + +###### TSourceValue + +`TSourceValue` + +###### TSubscribeSelected + +`TSubscribeSelected` + +##### Parameters + +###### props + +###### children + +(`state`) => `TemplateResult` \| `string` \| `TemplateResult` \| `string` + +###### selector + +(`state`) => `TSubscribeSelected` + +###### source + +[`SubscribeSource`](SubscribeSource.md)\<`TSourceValue`\> + +##### Returns + +`string` \| `TemplateResult` + +#### Call Signature + +```ts +(props): string | TemplateResult; +``` + +##### Type Parameters + +###### TSubscribeSelected + +`TSubscribeSelected` + +##### Parameters + +###### props + +###### children + +(`state`) => `TemplateResult` \| `string` \| `TemplateResult` \| `string` + +###### selector + +(`state`) => `TSubscribeSelected` + +##### Returns + +`string` \| `TemplateResult` + +#### Example + +```ts +table.Subscribe({ + selector: (state) => ({ rowSelection: state.rowSelection }), + children: (state) => html`
${JSON.stringify(state)}
`, +}) +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TSelected + +`TSelected` = `TableState`\<`TFeatures`\> diff --git a/docs/framework/lit/reference/type-aliases/SubscribeSource.md b/docs/framework/lit/reference/type-aliases/SubscribeSource.md new file mode 100644 index 0000000000..8a59c803bc --- /dev/null +++ b/docs/framework/lit/reference/type-aliases/SubscribeSource.md @@ -0,0 +1,22 @@ +--- +id: SubscribeSource +title: SubscribeSource +--- + +# Type Alias: SubscribeSource\ + +```ts +type SubscribeSource = + | Atom + | ReadonlyAtom + | Store +| ReadonlyStore; +``` + +Defined in: [TableController.ts:19](https://github.com/TanStack/table/blob/main/packages/lit-table/src/TableController.ts#L19) + +## Type Parameters + +### TValue + +`TValue` diff --git a/docs/framework/preact/guide/create-table-hook.md b/docs/framework/preact/guide/create-table-hook.md new file mode 100644 index 0000000000..138e749bc0 --- /dev/null +++ b/docs/framework/preact/guide/create-table-hook.md @@ -0,0 +1,169 @@ +--- +title: createTableHook Guide +--- + +`createTableHook` is an advanced API for building reusable, composable table configurations in Preact. It mirrors the [React `createTableHook` API](../react/guide/create-table-hook) — you define features, row models, and pre-bound components once, then reuse them across multiple tables with minimal boilerplate. + +> **When to use it:** Use `createTableHook` when you have multiple tables that share the same configuration. For a single table, `useTable` is sufficient. + +## Setup + +Create a shared table configuration file and call `createTableHook` with your features, row models, and component registries: + +```tsx +// hooks/table.ts + +import { + createTableHook, + tableFeatures, + columnFilteringFeature, + rowPaginationFeature, + rowSortingFeature, + createFilteredRowModel, + createPaginatedRowModel, + createSortedRowModel, + filterFns, + sortFns, +} from '@tanstack/preact-table' + +import { PaginationControls, RowCount, TableToolbar } from '../components/table-components' +import { TextCell, NumberCell, StatusCell } from '../components/cell-components' +import { SortIndicator, ColumnFilter } from '../components/header-components' + +export const { + createAppColumnHelper, + useAppTable, + useTableContext, + useCellContext, + useHeaderContext, +} = createTableHook({ + _features: tableFeatures({ + columnFilteringFeature, + rowPaginationFeature, + rowSortingFeature, + }), + + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + }, + + getRowId: (row) => row.id, + + tableComponents: { + PaginationControls, + RowCount, + TableToolbar, + }, + + cellComponents: { + TextCell, + NumberCell, + StatusCell, + }, + + headerComponents: { + SortIndicator, + ColumnFilter, + }, +}) +``` + +## What `createTableHook` Returns + +| Export | Description | +|--------|-------------| +| `useAppTable` | Hook for creating tables. Merges default options from the hook with per-table options. | +| `createAppColumnHelper` | Column helper with `TFeatures` pre-bound. Only requires `TData`. | +| `useTableContext` | Access the table instance inside `tableComponents`. | +| `useCellContext` | Access the cell instance inside `cellComponents`. | +| `useHeaderContext` | Access the header instance inside `headerComponents`. | + +## Component Registries + +The API matches React's `createTableHook`: + +- **`tableComponents`** — Components attached to the table (`table.PaginationControls`, etc.). Use `useTableContext()` inside them. +- **`cellComponents`** — Components attached to the cell (`cell.TextCell`, etc.). Use `useCellContext()` inside them. +- **`headerComponents`** — Components attached to the header (`header.SortIndicator`, etc.). Use `useHeaderContext()` inside them. + +## Using `useAppTable` + +```tsx +const personColumnHelper = createAppColumnHelper() + +function UsersTable() { + const [data, setData] = useState(() => makeData(100)) + + const columns = useMemo( + () => + personColumnHelper.columns([ + personColumnHelper.accessor('firstName', { + header: 'First Name', + cell: ({ cell }) => , + }), + personColumnHelper.accessor('age', { + header: 'Age', + cell: ({ cell }) => , + }), + ]), + [], + ) + + const table = useAppTable({ + columns, + data, + }) + + return ( + ({ pagination: state.pagination })}> + {() => ( +
+ setData(makeData(100))} /> + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((h) => ( + + {(header) => ( + + )} + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getAllCells().map((c) => ( + + {(cell) => } + + ))} + + ))} + +
+ + +
+ + +
+ )} +
+ ) +} +``` + +## AppTable, AppHeader, AppCell, AppFooter + +Same as React: `table.AppTable`, `table.AppHeader`, `table.AppCell`, and `table.AppFooter` provide context to your registered components. Use the `selector` prop on `AppTable` for optimized re-renders. + +## See Also + +- [React createTableHook Guide](../react/guide/create-table-hook) — The React guide has more detailed examples and the same API. +- [Composable Tables (React)](../react/examples/composable-tables) — Reference implementation (Preact API mirrors React). diff --git a/docs/framework/preact/guide/table-state.md b/docs/framework/preact/guide/table-state.md new file mode 100644 index 0000000000..0a78e85266 --- /dev/null +++ b/docs/framework/preact/guide/table-state.md @@ -0,0 +1,386 @@ +--- +title: Table State (Preact) Guide +--- + +## Examples + +Want to skip to the implementation? Check out these examples: + +- [Basic useTable](../examples/basic-use-table) +- [Basic External Atoms](../examples/basic-external-atoms) +- [Basic External State](../examples/basic-external-state) +- [Basic Subscribe](../examples/basic-subscribe) +- [With TanStack Query](../examples/with-tanstack-query) + +## Table State (Preact) Guide + +> **If you boil TanStack Table down to one sentence: TanStack Table is a large state-management coordinator for table states.** + +Understanding this guide is fundamental to understanding how TanStack Table works and how to interact with it for the best results. + +### Do you need to Manage External State? + +You usually do NOT need to manage table state yourself. If you pass nothing to `initialState`, `atoms`, `state`, or any of the `on[State]Change` table options, TanStack Table will manage its own state internally. + +There will be situations where you need to customize how you interact with the internal table state, or even hoist it up to your own scopes. TanStack Table lets you read, subscribe to, or own the state slices that matter to your app. This guide explains how table state works in Preact, how to read it, and when to use external atoms or external state. + +### State in v9 + +TanStack Table v9 overhauled state management around TanStack Store. TanStack Store uses the `alien-signals` implementation and supports performant derived state. For TanStack Table, this means the table can derive a full state store from independent state atoms and Preact can subscribe to only the pieces of table state that a component actually needs. + +A table instance has a few state surfaces: + +- `table.baseAtoms` are the internal writable atoms created from the resolved initial state. +- `table.atoms` are readonly derived atoms exposed per registered state slice. +- `table.store` is a readonly flat TanStack Store derived by putting all of the registered `table.atoms` together. +- `table.state` is Preact-only selected state. It is the value returned from the selector passed as the second argument to `useTable`. + +The Preact adapter mirrors the React adapter. It uses TanStack Store atoms for table state, `useSelector` for reactive Preact updates, and `table.Subscribe` for more targeted subscriptions. + +### Feature-based State + +State slices are only created for the features that are registered in `_features`. This keeps TanStack Table tree-shakeable and gives TypeScript more accurate state inference. + +For example, if `_features` includes `rowPaginationFeature`, TypeScript exposes pagination state APIs and `table.atoms.pagination`. If `_features` does not include `rowPaginationFeature`, `pagination` should not be available in `table.atoms`, `table.store.state`, `table.state`, `initialState`, `state`, or `atoms`. + +```tsx +const _features = tableFeatures({ + rowPaginationFeature, + rowSortingFeature, +}) + +const table = useTable({ + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, + columns, + data, +}) + +table.atoms.pagination.get() +table.atoms.sorting.get() + +// table.atoms.rowSelection // TypeScript error unless rowSelectionFeature is registered +``` + +### Accessing Table State + +There are two different questions when reading table state: + +- Do you only need the current value? +- Or should this Preact component re-render when that value changes? + +Use a direct atom or store read for the current value. Use a selector subscription for reactive rendering. + +#### Reading State Without Subscribing + +The simplest and most performant way to read a current state value is to read the matching atom: + +```tsx +const pagination = table.atoms.pagination.get() +const sorting = table.atoms.sorting.get() +``` + +You can also read the current flat store snapshot: + +```tsx +const tableState = table.store.state +const pagination = table.store.state.pagination +``` + +These reads are not Preact subscriptions. Calling `table.atoms.pagination.get()` or `table.store.state.pagination` during render reads the current value, but future changes will not automatically re-render that component unless something else causes a render. If the UI needs to stay reactive to table state changes, use `useTable` state selection, `table.Subscribe`, or even a `useSelector` hook from TanStack Store. + +#### Reading Reactive State with useTable + +The second argument to `useTable` is a TanStack Store selector. By default, the selector effectively selects all registered table state, so `table.state` contains the full state and the component re-renders when any selected state changes. + +You can pass your own selector to make `table.state` contain only the reactive state values that you want to cause re-renders. The Preact adapter compares selected values shallowly. The default selector selects all registered table state. + +```tsx +const table = useTable( + { + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data, + }, + (state) => ({ + pagination: state.pagination, + }), +) + +table.state.pagination +``` + +For large tables, you can also opt the parent table component out of table-state re-renders and subscribe lower in the tree: + +```tsx +const table = useTable( + { + _features, + _rowModels: {}, + columns, + data, + }, + () => null, +) +``` + +With this pattern, the parent component will not re-render for table state changes. Put reactive reads inside `table.Subscribe` where the UI actually needs them. + +#### Optimizing Re-renders with Selectors and table.Subscribe + +Use `table.Subscribe` when you want table-state re-renders to happen at a specific place in the Preact tree. This is useful for large or expensive tables, but it is usually something to reach for after the default `useTable` selector becomes a visible performance issue. + +Without a `source` prop, `table.Subscribe` subscribes to `table.store` and requires a selector. With a `source` prop, it can subscribe directly to one atom or store, such as `table.atoms.rowSelection`. + +```tsx +const table = useTable( + { + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data, + }, + () => null, +) + +return ( + ({ + columnFilters: state.columnFilters, + globalFilter: state.globalFilter, + pagination: state.pagination, + })} + > + {() => ( + + {table.getRowModel().rows.map((row) => ( + ... + ))} + + )} + +) +``` + +You can also subscribe directly to a single atom and select one value from it: + +```tsx + rowSelection[row.id]} +> + {(isSelected) => ( + + )} + +``` + +### Setting Table State + +You should almost never need to set table state directly. TanStack Table features expose dedicated APIs for interacting with their state, and those APIs are the safest way to make changes because they preserve the feature's own rules and related updates. + +```tsx +table.nextPage() +table.previousPage() +table.setPageIndex(0) +table.setPageSize(25) +``` + +The same idea applies across features. Use APIs like `table.setSorting(...)`, `table.setColumnFilters(...)`, `column.toggleVisibility()`, or `row.toggleSelected()` instead of manually editing the underlying state object. + +If you only care about setting starting values, use `initialState`. If you want to reset a state slice back to its initial value, use that feature's reset API. + +If you really do need to write a state slice directly, the low-level write surface for internally owned state is the matching base atom: + +```tsx +table.baseAtoms.pagination.set((old) => ({ + ...old, + pageIndex: 0, +})) +``` + +Direct base atom writes should be rare. If a slice is owned by an external atom passed through `atoms`, write to that external atom instead; `table.atoms.pagination` will read from the external atom, not the internal base atom. + +### Custom Initial State + +If you only need to customize the starting value for some table state, use `initialState`. You still do not need to manage that state yourself. + +`initialState` only applies to registered state slices. It is used to create the table's initial state and is also used by reset APIs such as `table.resetSorting()` or `table.resetPagination()`. Changing the `initialState` object later does not reset table state. + +```tsx +const table = useTable({ + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data, + initialState: { + sorting: [ + { + id: 'age', + desc: true, + }, + ], + pagination: { + pageIndex: 0, + pageSize: 25, + }, + }, +}) +``` + +> **Note:** Do not provide the same state slice in multiple ownership places unless you intentionally want one to win. For a slice like `pagination`, prefer exactly one of `initialState.pagination`, `atoms.pagination`, or `state.pagination` as the source of truth. External atoms take precedence over external `state`; external `state` syncs into the table's internal base atom. + +#### Resetting to Initial State + +Feature reset APIs reset to `table.initialState` by default. Many reset APIs also accept `true` to reset to that feature's blank/default state instead: + +```tsx +table.resetSorting() +table.resetPagination() +table.resetPagination(true) +``` + +Slice reset APIs like `resetPagination()` update through that feature's state updater and can update an externally owned atom. The core `table.reset()` API resets the internal base atoms, so do not use it as the primary way to reset state that is owned by external atoms. + +### Controlled State + +If you need easy access to table state in other parts of your application, you can control individual state slices. In v9, external atoms are the recommended way to do this because they preserve the atomic state model and let Preact subscribe to exactly the slices it needs. + +#### External Atoms + +Use external atoms when the app should own one or more table state slices. Create stable writable atoms with `useCreateAtom` from TanStack Store, pass them to the table's `atoms` option, and subscribe to them with `useSelector` anywhere else in your app. + +```tsx +import { useCreateAtom, useSelector } from '@tanstack/preact-store' +import { + rowPaginationFeature, + tableFeatures, + useTable, + type PaginationState, +} from '@tanstack/preact-table' + +const _features = tableFeatures({ + rowPaginationFeature, +}) + +function App() { + const paginationAtom = useCreateAtom({ + pageIndex: 0, + pageSize: 10, + }) + + const pagination = useSelector(paginationAtom) + + const dataQuery = useQuery({ + queryKey: ['data', pagination], + queryFn: () => fetchData(pagination), + }) + + const table = useTable({ + _features, + _rowModels: {}, + columns, + data: dataQuery.data?.rows ?? [], + rowCount: dataQuery.data?.rowCount, + atoms: { + pagination: paginationAtom, + }, + manualPagination: true, + }) + + // table pagination APIs update paginationAtom +} +``` + +When using the `atoms` option for a slice, you do not need to add the matching `on[State]Change` option. + +#### External State + +The classic `state` plus `on[State]Change` pattern is still supported. This can be convenient for simple integrations or when migrating v8 code, but it is less fine-grained than external atoms. + +```tsx +const [sorting, setSorting] = useState([]) +const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 10, +}) + +const table = useTable({ + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data, + state: { + sorting, + pagination, + }, + onSortingChange: setSorting, + onPaginationChange: setPagination, +}) +``` + +The v8-style `onStateChange` option is no longer part of the v9 `useTable` state model. v9 encourages keeping table state slices atomic and separated for performance. + +##### On State Change Callbacks + +The `on[State]Change` callbacks are useful when you are controlling a matching slice through the `state` option. They work like Preact state setters: an updater can be a raw value or a function that receives the previous value and returns the next value. + +If you provide an `on[State]Change` callback, also provide the corresponding value in `state`. For example, `onSortingChange` should be paired with `state.sorting`. + +```tsx +onPaginationChange: (updater) => { + setPagination((old) => { + const next = updater instanceof Function ? updater(old) : updater + + // side effects or validation can happen here + + return next + }) +} +``` + +### State Types + +Most complex states in TanStack Table have their own TypeScript types that you can import and use. + +```tsx +import { + useTable, + type PaginationState, + type RowSelectionState, + type SortingState, + type TableState, +} from '@tanstack/preact-table' + +const [sorting, setSorting] = useState([ + { + id: 'age', + desc: true, + }, +]) +``` + +`TableState` is inferred from the features registered on that table: + +```tsx +type MyTableState = TableState +``` diff --git a/docs/framework/preact/preact-table.md b/docs/framework/preact/preact-table.md new file mode 100644 index 0000000000..85975cd201 --- /dev/null +++ b/docs/framework/preact/preact-table.md @@ -0,0 +1,157 @@ +--- +title: Preact Table +--- + +The `@tanstack/preact-table` adapter wraps `@tanstack/table-core` with Preact-specific reactivity, rendering helpers, and types. It installs the Preact `coreReativityFeature` for you, so table state is backed by TanStack Store atoms while Preact components can subscribe through `useTable`, selectors, and `table.Subscribe`. + +TanStack Table v9 is explicit about what a table uses. Register features with `_features`, and register client-side row model factories with `_rowModels`. The core row model is included by default, so a basic table can use `_rowModels: {}`. + +## Creating a Table + +Use `useTable` to create a Preact table instance. + +```tsx +import { tableFeatures, useTable, type ColumnDef } from '@tanstack/preact-table' + +type Person = { + firstName: string + lastName: string + age: number +} + +const _features = tableFeatures({}) + +const columns: Array> = [ + { + accessorKey: 'firstName', + header: 'First name', + cell: (info) => info.getValue(), + }, +] + +function App({ data }: { data: Person[] }) { + const table = useTable({ + _features, + _rowModels: {}, + columns, + data, + }) + + return null +} +``` + +For feature-specific row models, register the feature and put the row model factory under `_rowModels`. + +```tsx +import { + createPaginatedRowModel, + createSortedRowModel, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/preact-table' + +const _features = tableFeatures({ + rowPaginationFeature, + rowSortingFeature, +}) + +const tableOptions = { + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, +} +``` + +## Table State + +Table state is managed with TanStack Store atoms in v9. For most tables, you do not need to manage table state yourself: set `initialState` when you need starting values, and use feature APIs like `table.nextPage()`, `table.setSorting(...)`, and `row.toggleSelected()` instead of mutating state directly. + +Use `atoms` when your app should own one state slice with TanStack Store. Use `state` with the matching `on[State]Change` option for simple Preact state integration or migration paths. + +```tsx +import { useCreateAtom } from '@tanstack/preact-store' +import { + rowPaginationFeature, + tableFeatures, + useTable, + type PaginationState, +} from '@tanstack/preact-table' + +const _features = tableFeatures({ + rowPaginationFeature, +}) + +function App({ data }: { data: Person[] }) { + const paginationAtom = useCreateAtom({ + pageIndex: 0, + pageSize: 10, + }) + + const table = useTable({ + _features, + _rowModels: {}, + columns, + data, + atoms: { + pagination: paginationAtom, + }, + }) + + return null +} +``` + +For reactive reads, the second argument to `useTable` selects from `table.store` and exposes the result on `table.state`. For large tables, `table.Subscribe` can subscribe smaller parts of the UI to selected state or individual atoms. See the [Table State Guide](./guide/table-state.md) and the [Basic Subscribe example](./examples/basic-subscribe). + +## Rendering Headers, Cells, and Footers + +Use `table.FlexRender` to render column `header`, `cell`, and `footer` definitions. It handles plain values and Preact components. + +```tsx + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + + + ))} + + ))} + +``` + +## createTableHook + +`createTableHook` creates an app-specific table hook. Use it when multiple tables should share `_features`, `_rowModels`, default options, column helpers, and component conventions. + +```tsx +import { createTableHook, tableFeatures } from '@tanstack/preact-table' + +const { useAppTable, createAppColumnHelper } = createTableHook({ + _features: tableFeatures({}), + _rowModels: {}, +}) + +const columnHelper = createAppColumnHelper() + +function App({ data }: { data: Person[] }) { + const table = useAppTable({ + columns, + data, + }) + + return null +} +``` + +See the [createTableHook Guide](./guide/create-table-hook.md) and the [Composable Tables example](./examples/composable-tables) for the full pattern. + +## API Reference + +See the [Preact API Reference](./reference/index.md). diff --git a/docs/framework/preact/reference/functions/FlexRender-1.md b/docs/framework/preact/reference/functions/FlexRender-1.md new file mode 100644 index 0000000000..706c45da04 --- /dev/null +++ b/docs/framework/preact/reference/functions/FlexRender-1.md @@ -0,0 +1,54 @@ +--- +id: FlexRender +title: FlexRender +--- + +# Function: FlexRender() + +```ts +function FlexRender(props): ComponentChild; +``` + +Defined in: [FlexRender.tsx:98](https://github.com/TanStack/table/blob/main/packages/preact-table/src/FlexRender.tsx#L98) + +Simplified component wrapper of `flexRender`. Use this utility component to render headers, cells, or footers with custom markup. +Only one prop (`cell`, `header`, or `footer`) may be passed. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### props + +[`FlexRenderProps`](../type-aliases/FlexRenderProps.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`ComponentChild` + +## Example + +```tsx + + + +``` + +This replaces calling `flexRender` directly like this: +```tsx +flexRender(cell.column.columnDef.cell, cell.getContext()) +flexRender(header.column.columnDef.header, header.getContext()) +flexRender(footer.column.columnDef.footer, footer.getContext()) +``` diff --git a/docs/framework/preact/reference/functions/Subscribe.md b/docs/framework/preact/reference/functions/Subscribe.md new file mode 100644 index 0000000000..3517e82bca --- /dev/null +++ b/docs/framework/preact/reference/functions/Subscribe.md @@ -0,0 +1,167 @@ +--- +id: Subscribe +title: Subscribe +--- + +# Function: Subscribe() + +## Call Signature + +```ts +function Subscribe(props): ComponentChildren; +``` + +Defined in: [Subscribe.ts:101](https://github.com/TanStack/table/blob/main/packages/preact-table/src/Subscribe.ts#L101) + +A Preact component that allows you to subscribe to the table state. + +For `table.Subscribe` from `useTable`, prefer that API — it uses overloads so +JSX contextual typing works. This standalone component uses a union `props` type. + +### Type Parameters + +#### TSourceValue + +`TSourceValue` + +### Parameters + +#### props + +[`SubscribePropsWithSourceIdentity`](../type-aliases/SubscribePropsWithSourceIdentity.md)\<`TSourceValue`\> + +### Returns + +`ComponentChildren` + +### Examples + +```tsx + ({ rowSelection: state.rowSelection })} +> + {({ rowSelection }) => ( +
Selected rows: {Object.keys(rowSelection).length}
+ )} +
+``` + +```tsx + rowSelection[row.id]} +> + {(selected) => } + +``` + +## Call Signature + +```ts +function Subscribe(props): ComponentChildren; +``` + +Defined in: [Subscribe.ts:104](https://github.com/TanStack/table/blob/main/packages/preact-table/src/Subscribe.ts#L104) + +A Preact component that allows you to subscribe to the table state. + +For `table.Subscribe` from `useTable`, prefer that API — it uses overloads so +JSX contextual typing works. This standalone component uses a union `props` type. + +### Type Parameters + +#### TSourceValue + +`TSourceValue` + +#### TSelected + +`TSelected` + +### Parameters + +#### props + +[`SubscribePropsWithSourceWithSelector`](../type-aliases/SubscribePropsWithSourceWithSelector.md)\<`TSourceValue`, `TSelected`\> + +### Returns + +`ComponentChildren` + +### Examples + +```tsx + ({ rowSelection: state.rowSelection })} +> + {({ rowSelection }) => ( +
Selected rows: {Object.keys(rowSelection).length}
+ )} +
+``` + +```tsx + rowSelection[row.id]} +> + {(selected) => } + +``` + +## Call Signature + +```ts +function Subscribe(props): ComponentChildren; +``` + +Defined in: [Subscribe.ts:107](https://github.com/TanStack/table/blob/main/packages/preact-table/src/Subscribe.ts#L107) + +A Preact component that allows you to subscribe to the table state. + +For `table.Subscribe` from `useTable`, prefer that API — it uses overloads so +JSX contextual typing works. This standalone component uses a union `props` type. + +### Type Parameters + +#### TFeatures + +`TFeatures` *extends* `TableFeatures` + +#### TSelected + +`TSelected` + +### Parameters + +#### props + +[`SubscribePropsWithStore`](../type-aliases/SubscribePropsWithStore.md)\<`TFeatures`, `TSelected`\> + +### Returns + +`ComponentChildren` + +### Examples + +```tsx + ({ rowSelection: state.rowSelection })} +> + {({ rowSelection }) => ( +
Selected rows: {Object.keys(rowSelection).length}
+ )} +
+``` + +```tsx + rowSelection[row.id]} +> + {(selected) => } + +``` diff --git a/docs/framework/preact/reference/functions/createTableHook.md b/docs/framework/preact/reference/functions/createTableHook.md new file mode 100644 index 0000000000..f6681de0eb --- /dev/null +++ b/docs/framework/preact/reference/functions/createTableHook.md @@ -0,0 +1,321 @@ +--- +id: createTableHook +title: createTableHook +--- + +# Function: createTableHook() + +```ts +function createTableHook(__namedParameters): object; +``` + +Defined in: [createTableHook.tsx:592](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L592) + +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 `useAppTable` hook that returns an extended table with App wrapper components +- Get a `createAppColumnHelper` function pre-bound to your features + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TTableComponents + +`TTableComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +## Parameters + +### \_\_namedParameters + +[`CreateTableHookOptions`](../type-aliases/CreateTableHookOptions.md)\<`TFeatures`, `TTableComponents`, `TCellComponents`, `THeaderComponents`\> + +## Returns + +### appFeatures + +```ts +appFeatures: TFeatures; +``` + +### createAppColumnHelper() + +```ts +createAppColumnHelper: () => AppColumnHelper; +``` + +Create a column helper pre-bound to the features and components configured in this table hook. +The cell, header, and footer contexts include pre-bound components (e.g., `cell.TextCell`). + +#### Type Parameters + +##### TData + +`TData` *extends* `RowData` + +#### Returns + +[`AppColumnHelper`](../type-aliases/AppColumnHelper.md)\<`TFeatures`, `TData`, `TCellComponents`, `THeaderComponents`\> + +#### Example + +```tsx +const columnHelper = createAppColumnHelper() + +const columns = [ + columnHelper.accessor('firstName', { + header: 'First Name', + cell: ({ cell }) => , // cell has pre-bound components! + }), + columnHelper.accessor('age', { + header: 'Age', + cell: ({ cell }) => , + }), +] +``` + +### useAppTable() + +```ts +useAppTable: (tableOptions, selector?) => AppPreactTable; +``` + +Enhanced useTable hook that returns a table with App wrapper components +and pre-bound tableComponents attached directly to the table object. + +Default options from createTableHook are automatically merged with +the options passed here. Options passed here take precedence. + +TFeatures is already known from the createTableHook call; TData is inferred from the data prop. + +#### Type Parameters + +##### TData + +`TData` *extends* `RowData` + +##### TSelected + +`TSelected` = `TableState`\<`TFeatures`\> + +#### Parameters + +##### tableOptions + +`Omit`\<`TableOptions`\<`TFeatures`, `TData`\>, `"_features"` \| `"_rowModels"`\> + +##### selector? + +(`state`) => `TSelected` + +#### Returns + +[`AppPreactTable`](../type-aliases/AppPreactTable.md)\<`TFeatures`, `TData`, `TSelected`, `TTableComponents`, `TCellComponents`, `THeaderComponents`\> + +### useCellContext() + +```ts +useCellContext: () => Cell; +``` + +Access the cell instance from within an `AppCell` wrapper. +Use this in custom `cellComponents` passed to `createTableHook`. +TFeatures is already known from the createTableHook call. + +#### Type Parameters + +##### TValue + +`TValue` *extends* `unknown` = `unknown` + +#### Returns + +`Cell`\<`TFeatures`, `any`, `TValue`\> + +#### Example + +```tsx +function TextCell() { + const cell = useCellContext() + return {cell.getValue()} +} + +function NumberCell({ format }: { format?: Intl.NumberFormatOptions }) { + const cell = useCellContext() + return {cell.getValue().toLocaleString(undefined, format)} +} +``` + +### useHeaderContext() + +```ts +useHeaderContext: () => Header; +``` + +Access the header instance from within an `AppHeader` or `AppFooter` wrapper. +Use this in custom `headerComponents` passed to `createTableHook`. +TFeatures is already known from the createTableHook call. + +#### Type Parameters + +##### TValue + +`TValue` *extends* `unknown` = `unknown` + +#### Returns + +`Header`\<`TFeatures`, `any`, `TValue`\> + +#### Example + +```tsx +function SortIndicator() { + const header = useHeaderContext() + const sorted = header.column.getIsSorted() + return sorted === 'asc' ? '🔼' : sorted === 'desc' ? '🔽' : null +} + +function ColumnFilter() { + const header = useHeaderContext() + if (!header.column.getCanFilter()) return null + return ( + header.column.setFilterValue(e.target.value)} + placeholder="Filter..." + /> + ) +} +``` + +### useTableContext() + +```ts +useTableContext: () => PreactTable; +``` + +Access the table instance from within an `AppTable` wrapper. +Use this in custom `tableComponents` passed to `createTableHook`. +TFeatures is already known from the createTableHook call. + +#### Type Parameters + +##### TData + +`TData` *extends* `RowData` = `RowData` + +#### Returns + +[`PreactTable`](../type-aliases/PreactTable.md)\<`TFeatures`, `TData`\> + +#### Example + +```tsx +function PaginationControls() { + const table = useTableContext() + return ( + s.pagination}> + {(pagination) => ( +
+ + Page {pagination.pageIndex + 1} + +
+ )} +
+ ) +} +``` + +## Example + +```tsx +// hooks/table.ts +export const { + useAppTable, + createAppColumnHelper, + useTableContext, + useCellContext, + useHeaderContext, +} = createTableHook({ + _features: tableFeatures({ + rowPaginationFeature, + rowSortingFeature, + columnFilteringFeature, + }), + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + filteredRowModel: createFilteredRowModel(filterFns), + }, + tableComponents: { PaginationControls, RowCount }, + cellComponents: { TextCell, NumberCell }, + headerComponents: { SortIndicator, ColumnFilter }, +}) + +// Create column helper with TFeatures already bound +const columnHelper = createAppColumnHelper() + +// components/table-components.tsx +function PaginationControls() { + const table = useTableContext() // TFeatures already known! + return s.pagination}>... +} + +// features/users.tsx +function UsersTable({ data }: { data: Person[] }) { + const table = useAppTable({ + columns, + data, // TData inferred from Person[] + }) + + return ( + + + + {table.getHeaderGroups().map(headerGroup => ( + + {headerGroup.headers.map(h => ( + + {(header) => ( + + )} + + ))} + + ))} + + + {table.getRowModel().rows.map(row => ( + + {row.getAllCells().map(c => ( + + {(cell) => } + + ))} + + ))} + +
+ + +
+ +
+ ) +} +``` diff --git a/docs/framework/preact/reference/functions/flexRender.md b/docs/framework/preact/reference/functions/flexRender.md new file mode 100644 index 0000000000..e4ec898268 --- /dev/null +++ b/docs/framework/preact/reference/functions/flexRender.md @@ -0,0 +1,40 @@ +--- +id: flexRender +title: flexRender +--- + +# Function: flexRender() + +```ts +function flexRender(Comp, props): ComponentChild | Element; +``` + +Defined in: [FlexRender.tsx:46](https://github.com/TanStack/table/blob/main/packages/preact-table/src/FlexRender.tsx#L46) + +If rendering headers, cells, or footers with custom markup, use flexRender instead of `cell.getValue()` or `cell.renderValue()`. + +## Type Parameters + +### TProps + +`TProps` *extends* `object` + +## Parameters + +### Comp + +[`Renderable`](../type-aliases/Renderable.md)\<`TProps`\> + +### props + +`TProps` + +## Returns + +`ComponentChild` \| `Element` + +## Example + +```ts +flexRender(cell.column.columnDef.cell, cell.getContext()) +``` diff --git a/docs/framework/preact/reference/functions/useTable.md b/docs/framework/preact/reference/functions/useTable.md new file mode 100644 index 0000000000..59ccf3e834 --- /dev/null +++ b/docs/framework/preact/reference/functions/useTable.md @@ -0,0 +1,64 @@ +--- +id: useTable +title: useTable +--- + +# Function: useTable() + +```ts +function useTable(tableOptions, selector?): PreactTable; +``` + +Defined in: [useTable.ts:103](https://github.com/TanStack/table/blob/main/packages/preact-table/src/useTable.ts#L103) + +Creates a Preact table instance backed by TanStack Store atoms. + +The optional selector projects from `table.store`; the selected value is +exposed on `table.state` and compared shallowly for Preact re-renders. Omit +the selector to subscribe to every registered table state slice, or pass a +narrower selector and use `table.Subscribe` lower in the tree for targeted +subscriptions. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TSelected + +`TSelected` = `TableState`\<`TFeatures`\> + +## Parameters + +### tableOptions + +`TableOptions`\<`TFeatures`, `TData`\> + +### selector? + +(`state`) => `TSelected` + +## Returns + +[`PreactTable`](../type-aliases/PreactTable.md)\<`TFeatures`, `TData`, `TSelected`\> + +## Example + +```tsx +const table = useTable( + { + _features, + _rowModels: {}, + columns, + data, + }, + (state) => ({ pagination: state.pagination }), +) + +table.state.pagination +``` diff --git a/docs/framework/preact/reference/index.md b/docs/framework/preact/reference/index.md new file mode 100644 index 0000000000..8b7a44d352 --- /dev/null +++ b/docs/framework/preact/reference/index.md @@ -0,0 +1,47 @@ +--- +id: "@tanstack/preact-table" +title: "@tanstack/preact-table" +--- + +# @tanstack/preact-table + +## Interfaces + +- [AppCellComponent](interfaces/AppCellComponent.md) +- [AppCellPropsWithoutSelector](interfaces/AppCellPropsWithoutSelector.md) +- [AppCellPropsWithSelector](interfaces/AppCellPropsWithSelector.md) +- [AppHeaderComponent](interfaces/AppHeaderComponent.md) +- [AppHeaderPropsWithoutSelector](interfaces/AppHeaderPropsWithoutSelector.md) +- [AppHeaderPropsWithSelector](interfaces/AppHeaderPropsWithSelector.md) +- [AppTableComponent](interfaces/AppTableComponent.md) +- [AppTablePropsWithoutSelector](interfaces/AppTablePropsWithoutSelector.md) +- [AppTablePropsWithSelector](interfaces/AppTablePropsWithSelector.md) + +## Type Aliases + +- [AppCellContext](type-aliases/AppCellContext.md) +- [AppColumnDefBase](type-aliases/AppColumnDefBase.md) +- [AppColumnDefTemplate](type-aliases/AppColumnDefTemplate.md) +- [AppColumnHelper](type-aliases/AppColumnHelper.md) +- [AppDisplayColumnDef](type-aliases/AppDisplayColumnDef.md) +- [AppGroupColumnDef](type-aliases/AppGroupColumnDef.md) +- [AppHeaderContext](type-aliases/AppHeaderContext.md) +- [AppPreactTable](type-aliases/AppPreactTable.md) +- [CreateTableHookOptions](type-aliases/CreateTableHookOptions.md) +- [FlexRenderProps](type-aliases/FlexRenderProps.md) +- [PreactTable](type-aliases/PreactTable.md) +- [Renderable](type-aliases/Renderable.md) +- [SubscribeProps](type-aliases/SubscribeProps.md) +- [SubscribePropsWithSource](type-aliases/SubscribePropsWithSource.md) +- [SubscribePropsWithSourceIdentity](type-aliases/SubscribePropsWithSourceIdentity.md) +- [SubscribePropsWithSourceWithSelector](type-aliases/SubscribePropsWithSourceWithSelector.md) +- [SubscribePropsWithStore](type-aliases/SubscribePropsWithStore.md) +- [SubscribeSource](type-aliases/SubscribeSource.md) + +## Functions + +- [createTableHook](functions/createTableHook.md) +- [flexRender](functions/flexRender.md) +- [FlexRender](functions/FlexRender-1.md) +- [Subscribe](functions/Subscribe.md) +- [useTable](functions/useTable.md) diff --git a/docs/framework/preact/reference/interfaces/AppCellComponent.md b/docs/framework/preact/reference/interfaces/AppCellComponent.md new file mode 100644 index 0000000000..1591d3e54f --- /dev/null +++ b/docs/framework/preact/reference/interfaces/AppCellComponent.md @@ -0,0 +1,80 @@ +--- +id: AppCellComponent +title: AppCellComponent +--- + +# Interface: AppCellComponent()\ + +Defined in: [createTableHook.tsx:368](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L368) + +Component type for AppCell - wraps a cell and provides cell context with optional Subscribe + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +## Call Signature + +```ts +AppCellComponent(props): ComponentChildren; +``` + +Defined in: [createTableHook.tsx:373](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L373) + +Component type for AppCell - wraps a cell and provides cell context with optional Subscribe + +### Type Parameters + +#### TValue + +`TValue` *extends* `unknown` = `unknown` + +### Parameters + +#### props + +[`AppCellPropsWithoutSelector`](AppCellPropsWithoutSelector.md)\<`TFeatures`, `TData`, `TValue`, `TCellComponents`\> + +### Returns + +`ComponentChildren` + +## Call Signature + +```ts +AppCellComponent(props): ComponentChildren; +``` + +Defined in: [createTableHook.tsx:381](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L381) + +Component type for AppCell - wraps a cell and provides cell context with optional Subscribe + +### Type Parameters + +#### TValue + +`TValue` *extends* `unknown` = `unknown` + +#### TSelected + +`TSelected` = `unknown` + +### Parameters + +#### props + +[`AppCellPropsWithSelector`](AppCellPropsWithSelector.md)\<`TFeatures`, `TData`, `TValue`, `TCellComponents`, `TSelected`\> + +### Returns + +`ComponentChildren` diff --git a/docs/framework/preact/reference/interfaces/AppCellPropsWithSelector.md b/docs/framework/preact/reference/interfaces/AppCellPropsWithSelector.md new file mode 100644 index 0000000000..342572ab7b --- /dev/null +++ b/docs/framework/preact/reference/interfaces/AppCellPropsWithSelector.md @@ -0,0 +1,86 @@ +--- +id: AppCellPropsWithSelector +title: AppCellPropsWithSelector +--- + +# Interface: AppCellPropsWithSelector\ + +Defined in: [createTableHook.tsx:313](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L313) + +Props for AppCell component - with selector + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +### TSelected + +`TSelected` + +## Properties + +### cell + +```ts +cell: Cell; +``` + +Defined in: [createTableHook.tsx:320](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L320) + +*** + +### children() + +```ts +children: (cell, state) => ComponentChildren; +``` + +Defined in: [createTableHook.tsx:321](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L321) + +#### Parameters + +##### cell + +`Cell_Cell`\<`TFeatures`, `TData`, `TValue`\> & `UnionToIntersection`\<`"columnGroupingFeature"` *extends* keyof `TFeatures` ? `Cell_ColumnGrouping` : `never`\> & `UnionToIntersection`\<\{ \[K in string \| number \| symbol\]: K extends "coreReativityFeature" ? never : TFeatures\[K\] extends TableFeature\ ? "Cell" extends keyof FeatureConstructorOptions ? FeatureConstructorOptions\[keyof (...) & "Cell"\] : never : any \}\[keyof `TFeatures`\]\> & `Cell_Plugins`\<`TFeatures`, `TData`, `TValue`\> & `TCellComponents` & `object` + +##### state + +`TSelected` + +#### Returns + +`ComponentChildren` + +*** + +### selector() + +```ts +selector: (state) => TSelected; +``` + +Defined in: [createTableHook.tsx:326](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L326) + +#### Parameters + +##### state + +`TableState`\<`TFeatures`\> + +#### Returns + +`TSelected` diff --git a/docs/framework/preact/reference/interfaces/AppCellPropsWithoutSelector.md b/docs/framework/preact/reference/interfaces/AppCellPropsWithoutSelector.md new file mode 100644 index 0000000000..da3737fabc --- /dev/null +++ b/docs/framework/preact/reference/interfaces/AppCellPropsWithoutSelector.md @@ -0,0 +1,68 @@ +--- +id: AppCellPropsWithoutSelector +title: AppCellPropsWithoutSelector +--- + +# Interface: AppCellPropsWithoutSelector\ + +Defined in: [createTableHook.tsx:296](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L296) + +Props for AppCell component - without selector + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +## Properties + +### cell + +```ts +cell: Cell; +``` + +Defined in: [createTableHook.tsx:302](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L302) + +*** + +### children() + +```ts +children: (cell) => ComponentChildren; +``` + +Defined in: [createTableHook.tsx:303](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L303) + +#### Parameters + +##### cell + +`Cell_Cell`\<`TFeatures`, `TData`, `TValue`\> & `UnionToIntersection`\<`"columnGroupingFeature"` *extends* keyof `TFeatures` ? `Cell_ColumnGrouping` : `never`\> & `UnionToIntersection`\<\{ \[K in string \| number \| symbol\]: K extends "coreReativityFeature" ? never : TFeatures\[K\] extends TableFeature\ ? "Cell" extends keyof FeatureConstructorOptions ? FeatureConstructorOptions\[keyof (...) & "Cell"\] : never : any \}\[keyof `TFeatures`\]\> & `Cell_Plugins`\<`TFeatures`, `TData`, `TValue`\> & `TCellComponents` & `object` + +#### Returns + +`ComponentChildren` + +*** + +### selector? + +```ts +optional selector: undefined; +``` + +Defined in: [createTableHook.tsx:307](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L307) diff --git a/docs/framework/preact/reference/interfaces/AppHeaderComponent.md b/docs/framework/preact/reference/interfaces/AppHeaderComponent.md new file mode 100644 index 0000000000..9425a4366c --- /dev/null +++ b/docs/framework/preact/reference/interfaces/AppHeaderComponent.md @@ -0,0 +1,80 @@ +--- +id: AppHeaderComponent +title: AppHeaderComponent +--- + +# Interface: AppHeaderComponent()\ + +Defined in: [createTableHook.tsx:395](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L395) + +Component type for AppHeader/AppFooter - wraps a header and provides header context with optional Subscribe + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +## Call Signature + +```ts +AppHeaderComponent(props): ComponentChildren; +``` + +Defined in: [createTableHook.tsx:400](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L400) + +Component type for AppHeader/AppFooter - wraps a header and provides header context with optional Subscribe + +### Type Parameters + +#### TValue + +`TValue` *extends* `unknown` = `unknown` + +### Parameters + +#### props + +[`AppHeaderPropsWithoutSelector`](AppHeaderPropsWithoutSelector.md)\<`TFeatures`, `TData`, `TValue`, `THeaderComponents`\> + +### Returns + +`ComponentChildren` + +## Call Signature + +```ts +AppHeaderComponent(props): ComponentChildren; +``` + +Defined in: [createTableHook.tsx:408](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L408) + +Component type for AppHeader/AppFooter - wraps a header and provides header context with optional Subscribe + +### Type Parameters + +#### TValue + +`TValue` *extends* `unknown` = `unknown` + +#### TSelected + +`TSelected` = `unknown` + +### Parameters + +#### props + +[`AppHeaderPropsWithSelector`](AppHeaderPropsWithSelector.md)\<`TFeatures`, `TData`, `TValue`, `THeaderComponents`, `TSelected`\> + +### Returns + +`ComponentChildren` diff --git a/docs/framework/preact/reference/interfaces/AppHeaderPropsWithSelector.md b/docs/framework/preact/reference/interfaces/AppHeaderPropsWithSelector.md new file mode 100644 index 0000000000..2717ebe2b8 --- /dev/null +++ b/docs/framework/preact/reference/interfaces/AppHeaderPropsWithSelector.md @@ -0,0 +1,88 @@ +--- +id: AppHeaderPropsWithSelector +title: AppHeaderPropsWithSelector +--- + +# Interface: AppHeaderPropsWithSelector\ + +Defined in: [createTableHook.tsx:349](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L349) + +Props for AppHeader/AppFooter component - with selector + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +### TSelected + +`TSelected` + +## Properties + +### children() + +```ts +children: (header, state) => ComponentChildren; +``` + +Defined in: [createTableHook.tsx:357](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L357) + +#### Parameters + +##### header + +`Header_Core`\<`TFeatures`, `TData`, `TValue`\> & `UnionToIntersection`\< + \| `"columnSizingFeature"` *extends* keyof `TFeatures` ? `Header_ColumnSizing` : `never` + \| `"columnResizingFeature"` *extends* keyof `TFeatures` ? `Header_ColumnResizing` : `never`\> & `UnionToIntersection`\<\{ \[K in string \| number \| symbol\]: K extends "coreReativityFeature" ? never : TFeatures\[K\] extends TableFeature\ ? "Header" extends keyof FeatureConstructorOptions ? FeatureConstructorOptions\[keyof (...) & "Header"\] : never : any \}\[keyof `TFeatures`\]\> & `Header_Plugins`\<`TFeatures`, `TData`, `TValue`\> & `THeaderComponents` & `object` + +##### state + +`TSelected` + +#### Returns + +`ComponentChildren` + +*** + +### header + +```ts +header: Header; +``` + +Defined in: [createTableHook.tsx:356](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L356) + +*** + +### selector() + +```ts +selector: (state) => TSelected; +``` + +Defined in: [createTableHook.tsx:362](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L362) + +#### Parameters + +##### state + +`TableState`\<`TFeatures`\> + +#### Returns + +`TSelected` diff --git a/docs/framework/preact/reference/interfaces/AppHeaderPropsWithoutSelector.md b/docs/framework/preact/reference/interfaces/AppHeaderPropsWithoutSelector.md new file mode 100644 index 0000000000..4d2db8ee90 --- /dev/null +++ b/docs/framework/preact/reference/interfaces/AppHeaderPropsWithoutSelector.md @@ -0,0 +1,70 @@ +--- +id: AppHeaderPropsWithoutSelector +title: AppHeaderPropsWithoutSelector +--- + +# Interface: AppHeaderPropsWithoutSelector\ + +Defined in: [createTableHook.tsx:332](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L332) + +Props for AppHeader/AppFooter component - without selector + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +## Properties + +### children() + +```ts +children: (header) => ComponentChildren; +``` + +Defined in: [createTableHook.tsx:339](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L339) + +#### Parameters + +##### header + +`Header_Core`\<`TFeatures`, `TData`, `TValue`\> & `UnionToIntersection`\< + \| `"columnSizingFeature"` *extends* keyof `TFeatures` ? `Header_ColumnSizing` : `never` + \| `"columnResizingFeature"` *extends* keyof `TFeatures` ? `Header_ColumnResizing` : `never`\> & `UnionToIntersection`\<\{ \[K in string \| number \| symbol\]: K extends "coreReativityFeature" ? never : TFeatures\[K\] extends TableFeature\ ? "Header" extends keyof FeatureConstructorOptions ? FeatureConstructorOptions\[keyof (...) & "Header"\] : never : any \}\[keyof `TFeatures`\]\> & `Header_Plugins`\<`TFeatures`, `TData`, `TValue`\> & `THeaderComponents` & `object` + +#### Returns + +`ComponentChildren` + +*** + +### header + +```ts +header: Header; +``` + +Defined in: [createTableHook.tsx:338](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L338) + +*** + +### selector? + +```ts +optional selector: undefined; +``` + +Defined in: [createTableHook.tsx:343](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L343) diff --git a/docs/framework/preact/reference/interfaces/AppTableComponent.md b/docs/framework/preact/reference/interfaces/AppTableComponent.md new file mode 100644 index 0000000000..ff716db2b3 --- /dev/null +++ b/docs/framework/preact/reference/interfaces/AppTableComponent.md @@ -0,0 +1,62 @@ +--- +id: AppTableComponent +title: AppTableComponent +--- + +# Interface: AppTableComponent()\ + +Defined in: [createTableHook.tsx:422](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L422) + +Component type for AppTable - root wrapper with optional Subscribe + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +## Call Signature + +```ts +AppTableComponent(props): ComponentChildren; +``` + +Defined in: [createTableHook.tsx:423](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L423) + +Component type for AppTable - root wrapper with optional Subscribe + +### Parameters + +#### props + +[`AppTablePropsWithoutSelector`](AppTablePropsWithoutSelector.md) + +### Returns + +`ComponentChildren` + +## Call Signature + +```ts +AppTableComponent(props): ComponentChildren; +``` + +Defined in: [createTableHook.tsx:424](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L424) + +Component type for AppTable - root wrapper with optional Subscribe + +### Type Parameters + +#### TSelected + +`TSelected` + +### Parameters + +#### props + +[`AppTablePropsWithSelector`](AppTablePropsWithSelector.md)\<`TFeatures`, `TSelected`\> + +### Returns + +`ComponentChildren` diff --git a/docs/framework/preact/reference/interfaces/AppTablePropsWithSelector.md b/docs/framework/preact/reference/interfaces/AppTablePropsWithSelector.md new file mode 100644 index 0000000000..fc81aec575 --- /dev/null +++ b/docs/framework/preact/reference/interfaces/AppTablePropsWithSelector.md @@ -0,0 +1,60 @@ +--- +id: AppTablePropsWithSelector +title: AppTablePropsWithSelector +--- + +# Interface: AppTablePropsWithSelector\ + +Defined in: [createTableHook.tsx:285](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L285) + +Props for AppTable component - with selector + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TSelected + +`TSelected` + +## Properties + +### children() + +```ts +children: (state) => ComponentChildren; +``` + +Defined in: [createTableHook.tsx:289](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L289) + +#### Parameters + +##### state + +`TSelected` + +#### Returns + +`ComponentChildren` + +*** + +### selector() + +```ts +selector: (state) => TSelected; +``` + +Defined in: [createTableHook.tsx:290](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L290) + +#### Parameters + +##### state + +`TableState`\<`TFeatures`\> + +#### Returns + +`TSelected` diff --git a/docs/framework/preact/reference/interfaces/AppTablePropsWithoutSelector.md b/docs/framework/preact/reference/interfaces/AppTablePropsWithoutSelector.md new file mode 100644 index 0000000000..21193bc909 --- /dev/null +++ b/docs/framework/preact/reference/interfaces/AppTablePropsWithoutSelector.md @@ -0,0 +1,30 @@ +--- +id: AppTablePropsWithoutSelector +title: AppTablePropsWithoutSelector +--- + +# Interface: AppTablePropsWithoutSelector + +Defined in: [createTableHook.tsx:277](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L277) + +Props for AppTable component - without selector + +## Properties + +### children + +```ts +children: ComponentChildren; +``` + +Defined in: [createTableHook.tsx:278](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L278) + +*** + +### selector? + +```ts +optional selector: undefined; +``` + +Defined in: [createTableHook.tsx:279](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L279) diff --git a/docs/framework/preact/reference/type-aliases/AppCellContext.md b/docs/framework/preact/reference/type-aliases/AppCellContext.md new file mode 100644 index 0000000000..db8a6b5f8b --- /dev/null +++ b/docs/framework/preact/reference/type-aliases/AppCellContext.md @@ -0,0 +1,105 @@ +--- +id: AppCellContext +title: AppCellContext +--- + +# Type Alias: AppCellContext\ + +```ts +type AppCellContext = object; +``` + +Defined in: [createTableHook.tsx:41](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L41) + +Enhanced CellContext with pre-bound cell components. +The `cell` property includes the registered cellComponents. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +## Properties + +### cell + +```ts +cell: Cell & TCellComponents & object; +``` + +Defined in: [createTableHook.tsx:47](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L47) + +#### Type Declaration + +##### FlexRender() + +```ts +FlexRender: () => ComponentChildren; +``` + +###### Returns + +`ComponentChildren` + +*** + +### column + +```ts +column: Column; +``` + +Defined in: [createTableHook.tsx:49](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L49) + +*** + +### getValue + +```ts +getValue: CellContext["getValue"]; +``` + +Defined in: [createTableHook.tsx:50](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L50) + +*** + +### renderValue + +```ts +renderValue: CellContext["renderValue"]; +``` + +Defined in: [createTableHook.tsx:51](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L51) + +*** + +### row + +```ts +row: Row; +``` + +Defined in: [createTableHook.tsx:52](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L52) + +*** + +### table + +```ts +table: Table; +``` + +Defined in: [createTableHook.tsx:53](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L53) diff --git a/docs/framework/preact/reference/type-aliases/AppColumnDefBase.md b/docs/framework/preact/reference/type-aliases/AppColumnDefBase.md new file mode 100644 index 0000000000..b17e5b699b --- /dev/null +++ b/docs/framework/preact/reference/type-aliases/AppColumnDefBase.md @@ -0,0 +1,56 @@ +--- +id: AppColumnDefBase +title: AppColumnDefBase +--- + +# Type Alias: AppColumnDefBase\ + +```ts +type AppColumnDefBase = Omit, "cell" | "header" | "footer"> & object; +``` + +Defined in: [createTableHook.tsx:86](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L86) + +Enhanced column definition base with pre-bound components in cell/header/footer contexts. + +## Type Declaration + +### cell? + +```ts +optional cell: AppColumnDefTemplate>; +``` + +### footer? + +```ts +optional footer: AppColumnDefTemplate>; +``` + +### header? + +```ts +optional header: AppColumnDefTemplate>; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> diff --git a/docs/framework/preact/reference/type-aliases/AppColumnDefTemplate.md b/docs/framework/preact/reference/type-aliases/AppColumnDefTemplate.md new file mode 100644 index 0000000000..36ebc3b007 --- /dev/null +++ b/docs/framework/preact/reference/type-aliases/AppColumnDefTemplate.md @@ -0,0 +1,20 @@ +--- +id: AppColumnDefTemplate +title: AppColumnDefTemplate +--- + +# Type Alias: AppColumnDefTemplate\ + +```ts +type AppColumnDefTemplate = string | (props) => any; +``` + +Defined in: [createTableHook.tsx:79](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L79) + +Template type for column definitions that can be a string or a function. + +## Type Parameters + +### TProps + +`TProps` *extends* `object` diff --git a/docs/framework/preact/reference/type-aliases/AppColumnHelper.md b/docs/framework/preact/reference/type-aliases/AppColumnHelper.md new file mode 100644 index 0000000000..8831121a72 --- /dev/null +++ b/docs/framework/preact/reference/type-aliases/AppColumnHelper.md @@ -0,0 +1,144 @@ +--- +id: AppColumnHelper +title: AppColumnHelper +--- + +# Type Alias: AppColumnHelper\ + +```ts +type AppColumnHelper = object; +``` + +Defined in: [createTableHook.tsx:162](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L162) + +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 Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +## Properties + +### accessor() + +```ts +accessor: (accessor, column) => TAccessor extends AccessorFn ? AccessorFnColumnDef : AccessorKeyColumnDef; +``` + +Defined in: [createTableHook.tsx:172](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L172) + +Creates a data column definition with an accessor key or function. +The cell, header, and footer contexts include pre-bound components. + +#### Type Parameters + +##### TAccessor + +`TAccessor` *extends* `AccessorFn`\<`TData`\> \| `DeepKeys`\<`TData`\> + +##### TValue + +`TValue` *extends* `TAccessor` *extends* `AccessorFn`\<`TData`, infer TReturn\> ? `TReturn` : `TAccessor` *extends* `DeepKeys`\<`TData`\> ? `DeepValue`\<`TData`, `TAccessor`\> : `never` + +#### Parameters + +##### accessor + +`TAccessor` + +##### column + +`TAccessor` *extends* `AccessorFn`\<`TData`\> ? [`AppColumnDefBase`](AppColumnDefBase.md)\<`TFeatures`, `TData`, `TValue`, `TCellComponents`, `THeaderComponents`\> & `object` : [`AppColumnDefBase`](AppColumnDefBase.md)\<`TFeatures`, `TData`, `TValue`, `TCellComponents`, `THeaderComponents`\> + +#### Returns + +`TAccessor` *extends* `AccessorFn`\<`TData`\> ? `AccessorFnColumnDef`\<`TFeatures`, `TData`, `TValue`\> : `AccessorKeyColumnDef`\<`TFeatures`, `TData`, `TValue`\> + +*** + +### columns() + +```ts +columns: (columns) => ColumnDef[] & [...TColumns]; +``` + +Defined in: [createTableHook.tsx:203](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L203) + +Wraps an array of column definitions to preserve each column's individual TValue type. + +#### Type Parameters + +##### TColumns + +`TColumns` *extends* `ReadonlyArray`\<`ColumnDef`\<`TFeatures`, `TData`, `any`\>\> + +#### Parameters + +##### columns + +\[`...TColumns`\] + +#### Returns + +`ColumnDef`\<`TFeatures`, `TData`, `any`\>[] & \[`...TColumns`\] + +*** + +### display() + +```ts +display: (column) => DisplayColumnDef; +``` + +Defined in: [createTableHook.tsx:211](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L211) + +Creates a display column definition for non-data columns. +The cell, header, and footer contexts include pre-bound components. + +#### Parameters + +##### column + +[`AppDisplayColumnDef`](AppDisplayColumnDef.md)\<`TFeatures`, `TData`, `TCellComponents`, `THeaderComponents`\> + +#### Returns + +`DisplayColumnDef`\<`TFeatures`, `TData`, `unknown`\> + +*** + +### group() + +```ts +group: (column) => GroupColumnDef; +``` + +Defined in: [createTableHook.tsx:224](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L224) + +Creates a group column definition with nested child columns. +The cell, header, and footer contexts include pre-bound components. + +#### Parameters + +##### column + +[`AppGroupColumnDef`](AppGroupColumnDef.md)\<`TFeatures`, `TData`, `TCellComponents`, `THeaderComponents`\> + +#### Returns + +`GroupColumnDef`\<`TFeatures`, `TData`, `unknown`\> diff --git a/docs/framework/preact/reference/type-aliases/AppDisplayColumnDef.md b/docs/framework/preact/reference/type-aliases/AppDisplayColumnDef.md new file mode 100644 index 0000000000..20786b81a8 --- /dev/null +++ b/docs/framework/preact/reference/type-aliases/AppDisplayColumnDef.md @@ -0,0 +1,52 @@ +--- +id: AppDisplayColumnDef +title: AppDisplayColumnDef +--- + +# Type Alias: AppDisplayColumnDef\ + +```ts +type AppDisplayColumnDef = Omit, "cell" | "header" | "footer"> & object; +``` + +Defined in: [createTableHook.tsx:110](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L110) + +Enhanced display column definition with pre-bound components. + +## Type Declaration + +### cell? + +```ts +optional cell: AppColumnDefTemplate>; +``` + +### footer? + +```ts +optional footer: AppColumnDefTemplate>; +``` + +### header? + +```ts +optional header: AppColumnDefTemplate>; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> diff --git a/docs/framework/preact/reference/type-aliases/AppGroupColumnDef.md b/docs/framework/preact/reference/type-aliases/AppGroupColumnDef.md new file mode 100644 index 0000000000..366b601f2a --- /dev/null +++ b/docs/framework/preact/reference/type-aliases/AppGroupColumnDef.md @@ -0,0 +1,58 @@ +--- +id: AppGroupColumnDef +title: AppGroupColumnDef +--- + +# Type Alias: AppGroupColumnDef\ + +```ts +type AppGroupColumnDef = Omit, "cell" | "header" | "footer" | "columns"> & object; +``` + +Defined in: [createTableHook.tsx:133](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L133) + +Enhanced group column definition with pre-bound components. + +## Type Declaration + +### cell? + +```ts +optional cell: AppColumnDefTemplate>; +``` + +### columns? + +```ts +optional columns: ColumnDef[]; +``` + +### footer? + +```ts +optional footer: AppColumnDefTemplate>; +``` + +### header? + +```ts +optional header: AppColumnDefTemplate>; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> diff --git a/docs/framework/preact/reference/type-aliases/AppHeaderContext.md b/docs/framework/preact/reference/type-aliases/AppHeaderContext.md new file mode 100644 index 0000000000..e6cfd77c2a --- /dev/null +++ b/docs/framework/preact/reference/type-aliases/AppHeaderContext.md @@ -0,0 +1,75 @@ +--- +id: AppHeaderContext +title: AppHeaderContext +--- + +# Type Alias: AppHeaderContext\ + +```ts +type AppHeaderContext = object; +``` + +Defined in: [createTableHook.tsx:60](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L60) + +Enhanced HeaderContext with pre-bound header components. +The `header` property includes the registered headerComponents. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +## Properties + +### column + +```ts +column: Column; +``` + +Defined in: [createTableHook.tsx:66](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L66) + +*** + +### header + +```ts +header: Header & THeaderComponents & object; +``` + +Defined in: [createTableHook.tsx:67](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L67) + +#### Type Declaration + +##### FlexRender() + +```ts +FlexRender: () => ComponentChildren; +``` + +###### Returns + +`ComponentChildren` + +*** + +### table + +```ts +table: Table; +``` + +Defined in: [createTableHook.tsx:69](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L69) diff --git a/docs/framework/preact/reference/type-aliases/AppPreactTable.md b/docs/framework/preact/reference/type-aliases/AppPreactTable.md new file mode 100644 index 0000000000..2684fc1378 --- /dev/null +++ b/docs/framework/preact/reference/type-aliases/AppPreactTable.md @@ -0,0 +1,127 @@ +--- +id: AppPreactTable +title: AppPreactTable +--- + +# Type Alias: AppPreactTable\ + +```ts +type AppPreactTable = PreactTable & NoInfer & object; +``` + +Defined in: [createTableHook.tsx:432](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L432) + +Extended table API returned by useAppTable with all App wrapper components + +## Type Declaration + +### AppCell + +```ts +AppCell: AppCellComponent>; +``` + +Wraps a cell and provides cell context with pre-bound cellComponents. +Optionally accepts a selector for Subscribe functionality. + +#### Example + +```tsx +// Without selector + + {(c) => } + + +// With selector - children receives cell and selected state + s.columnFilters}> + {(c, filters) => {filters.length}} + +``` + +### AppFooter + +```ts +AppFooter: AppHeaderComponent>; +``` + +Wraps a footer and provides header context with pre-bound headerComponents. +Optionally accepts a selector for Subscribe functionality. + +#### Example + +```tsx + + {(f) => } + +``` + +### AppHeader + +```ts +AppHeader: AppHeaderComponent>; +``` + +Wraps a header and provides header context with pre-bound headerComponents. +Optionally accepts a selector for Subscribe functionality. + +#### Example + +```tsx +// Without selector + + {(h) => } + + +// With selector + s.sorting}> + {(h, sorting) => {sorting.length} sorted} + +``` + +### AppTable + +```ts +AppTable: AppTableComponent; +``` + +Root wrapper component that provides table context with optional Subscribe. + +#### Example + +```tsx +// Without selector - children is ComponentChildren + + ...
+
+ +// With selector - children receives selected state + s.pagination}> + {(pagination) =>
Page {pagination.pageIndex}
} +
+``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TSelected + +`TSelected` + +### TTableComponents + +`TTableComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> diff --git a/docs/framework/preact/reference/type-aliases/CreateTableHookOptions.md b/docs/framework/preact/reference/type-aliases/CreateTableHookOptions.md new file mode 100644 index 0000000000..db1ca79acc --- /dev/null +++ b/docs/framework/preact/reference/type-aliases/CreateTableHookOptions.md @@ -0,0 +1,83 @@ +--- +id: CreateTableHookOptions +title: CreateTableHookOptions +--- + +# Type Alias: CreateTableHookOptions\ + +```ts +type CreateTableHookOptions = Omit, "columns" | "data" | "store" | "state" | "initialState"> & object; +``` + +Defined in: [createTableHook.tsx:242](https://github.com/TanStack/table/blob/main/packages/preact-table/src/createTableHook.tsx#L242) + +Options for creating a table hook with pre-bound components and default table options. +Extends all TableOptions except 'columns' | 'data' | 'store' | 'state' | 'initialState'. + +## Type Declaration + +### cellComponents? + +```ts +optional cellComponents: TCellComponents; +``` + +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 + +```ts +{ TextCell, NumberCell, DateCell, CurrencyCell } +``` + +### headerComponents? + +```ts +optional headerComponents: THeaderComponents; +``` + +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 + +```ts +{ SortIndicator, ColumnFilter, ResizeHandle } +``` + +### tableComponents? + +```ts +optional tableComponents: TTableComponents; +``` + +Table-level components that need access to the table instance. +These are available directly on the table object returned by useAppTable. +Use `useTableContext()` inside these components. + +#### Example + +```ts +{ PaginationControls, GlobalFilter, RowCount } +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TTableComponents + +`TTableComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> diff --git a/docs/framework/preact/reference/type-aliases/FlexRenderProps.md b/docs/framework/preact/reference/type-aliases/FlexRenderProps.md new file mode 100644 index 0000000000..cfb08d16a6 --- /dev/null +++ b/docs/framework/preact/reference/type-aliases/FlexRenderProps.md @@ -0,0 +1,58 @@ +--- +id: FlexRenderProps +title: FlexRenderProps +--- + +# Type Alias: FlexRenderProps\ + +```ts +type FlexRenderProps = + | { + cell: Cell; + footer?: never; + header?: never; +} + | { + cell?: never; + footer?: never; + header: Header; +} + | { + cell?: never; + footer: Header; + header?: never; +}; +``` + +Defined in: [FlexRender.tsx:64](https://github.com/TanStack/table/blob/main/packages/preact-table/src/FlexRender.tsx#L64) + +Simplified component wrapper of `flexRender`. Use this utility component to render headers, cells, or footers with custom markup. +Only one prop (`cell`, `header`, or `footer`) may be passed. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` = `CellData` + +## Examples + +```ts + +``` + +```ts + +``` + +```ts + +``` diff --git a/docs/framework/preact/reference/type-aliases/PreactTable.md b/docs/framework/preact/reference/type-aliases/PreactTable.md new file mode 100644 index 0000000000..99c7f20b1f --- /dev/null +++ b/docs/framework/preact/reference/type-aliases/PreactTable.md @@ -0,0 +1,164 @@ +--- +id: PreactTable +title: PreactTable +--- + +# Type Alias: PreactTable\ + +```ts +type PreactTable = Table & object; +``` + +Defined in: [useTable.ts:19](https://github.com/TanStack/table/blob/main/packages/preact-table/src/useTable.ts#L19) + +## Type Declaration + +### FlexRender() + +```ts +FlexRender: (props) => ComponentChildren; +``` + +A Preact component that renders headers, cells, or footers with custom markup. +Use this utility component instead of manually calling flexRender. + +#### Type Parameters + +##### TValue + +`TValue` *extends* `CellData` = `CellData` + +#### Parameters + +##### props + +[`FlexRenderProps`](FlexRenderProps.md)\<`TFeatures`, `TData`, `TValue`\> + +#### Returns + +`ComponentChildren` + +### state + +```ts +readonly state: Readonly; +``` + +The selected state of the table. This state may not match the structure of `table.store.state` because it is selected by the `selector` function that you pass as the 2nd argument to `useTable`. + +### Subscribe() + +```ts +Subscribe: { + (props): ComponentChildren; + (props): ComponentChildren; + (props): ComponentChildren; +}; +``` + +Overloads (source first, then store) so JSX contextual typing works for both modes. +Source without `selector` is separate so children infer `TSourceValue` (identity projection). + +#### Call Signature + +```ts +(props): ComponentChildren; +``` + +##### Type Parameters + +###### TSourceValue + +`TSourceValue` + +##### Parameters + +###### props + +###### children + +(`state`) => `ComponentChildren` \| `ComponentChildren` + +###### selector? + +`undefined` + +###### source + +[`SubscribeSource`](SubscribeSource.md)\<`TSourceValue`\> + +##### Returns + +`ComponentChildren` + +#### Call Signature + +```ts +(props): ComponentChildren; +``` + +##### Type Parameters + +###### TSourceValue + +`TSourceValue` + +###### TSubSelected + +`TSubSelected` + +##### Parameters + +###### props + +###### children + +(`state`) => `ComponentChildren` \| `ComponentChildren` + +###### selector + +(`state`) => `TSubSelected` + +###### source + +[`SubscribeSource`](SubscribeSource.md)\<`TSourceValue`\> + +##### Returns + +`ComponentChildren` + +#### Call Signature + +```ts +(props): ComponentChildren; +``` + +##### Type Parameters + +###### TSubSelected + +`TSubSelected` + +##### Parameters + +###### props + +`Omit`\<[`SubscribePropsWithStore`](SubscribePropsWithStore.md)\<`TFeatures`, `TSubSelected`\>, `"source"`\> + +##### Returns + +`ComponentChildren` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TSelected + +`TSelected` = `TableState`\<`TFeatures`\> diff --git a/docs/framework/preact/reference/type-aliases/Renderable.md b/docs/framework/preact/reference/type-aliases/Renderable.md new file mode 100644 index 0000000000..e9f9ef4d66 --- /dev/null +++ b/docs/framework/preact/reference/type-aliases/Renderable.md @@ -0,0 +1,18 @@ +--- +id: Renderable +title: Renderable +--- + +# Type Alias: Renderable\ + +```ts +type Renderable = ComponentChild | ComponentType; +``` + +Defined in: [FlexRender.tsx:10](https://github.com/TanStack/table/blob/main/packages/preact-table/src/FlexRender.tsx#L10) + +## Type Parameters + +### TProps + +`TProps` diff --git a/docs/framework/preact/reference/type-aliases/SubscribeProps.md b/docs/framework/preact/reference/type-aliases/SubscribeProps.md new file mode 100644 index 0000000000..b12a36c221 --- /dev/null +++ b/docs/framework/preact/reference/type-aliases/SubscribeProps.md @@ -0,0 +1,29 @@ +--- +id: SubscribeProps +title: SubscribeProps +--- + +# Type Alias: SubscribeProps\ + +```ts +type SubscribeProps = + | SubscribePropsWithStore + | SubscribePropsWithSourceIdentity +| SubscribePropsWithSourceWithSelector; +``` + +Defined in: [Subscribe.ts:64](https://github.com/TanStack/table/blob/main/packages/preact-table/src/Subscribe.ts#L64) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TSelected + +`TSelected` = `unknown` + +### TSourceValue + +`TSourceValue` = `unknown` diff --git a/docs/framework/preact/reference/type-aliases/SubscribePropsWithSource.md b/docs/framework/preact/reference/type-aliases/SubscribePropsWithSource.md new file mode 100644 index 0000000000..d6a2e60daf --- /dev/null +++ b/docs/framework/preact/reference/type-aliases/SubscribePropsWithSource.md @@ -0,0 +1,26 @@ +--- +id: SubscribePropsWithSource +title: SubscribePropsWithSource +--- + +# Type Alias: SubscribePropsWithSource\ + +```ts +type SubscribePropsWithSource = + | SubscribePropsWithSourceIdentity +| SubscribePropsWithSourceWithSelector; +``` + +Defined in: [Subscribe.ts:60](https://github.com/TanStack/table/blob/main/packages/preact-table/src/Subscribe.ts#L60) + +Subscribe to a single source — atom or store (identity or projected). + +## Type Parameters + +### TSourceValue + +`TSourceValue` + +### TSelected + +`TSelected` = `TSourceValue` diff --git a/docs/framework/preact/reference/type-aliases/SubscribePropsWithSourceIdentity.md b/docs/framework/preact/reference/type-aliases/SubscribePropsWithSourceIdentity.md new file mode 100644 index 0000000000..347a1565da --- /dev/null +++ b/docs/framework/preact/reference/type-aliases/SubscribePropsWithSourceIdentity.md @@ -0,0 +1,52 @@ +--- +id: SubscribePropsWithSourceIdentity +title: SubscribePropsWithSourceIdentity +--- + +# Type Alias: SubscribePropsWithSourceIdentity\ + +```ts +type SubscribePropsWithSourceIdentity = object; +``` + +Defined in: [Subscribe.ts:42](https://github.com/TanStack/table/blob/main/packages/preact-table/src/Subscribe.ts#L42) + +Subscribe to the full value of a source (e.g. `table.atoms.rowSelection` or +`table.optionsStore`). Omitting `selector` is equivalent to the identity +selector — children receive `TSourceValue`. + +## Type Parameters + +### TSourceValue + +`TSourceValue` + +## Properties + +### children + +```ts +children: (state) => ComponentChildren | ComponentChildren; +``` + +Defined in: [Subscribe.ts:45](https://github.com/TanStack/table/blob/main/packages/preact-table/src/Subscribe.ts#L45) + +*** + +### selector? + +```ts +optional selector: undefined; +``` + +Defined in: [Subscribe.ts:44](https://github.com/TanStack/table/blob/main/packages/preact-table/src/Subscribe.ts#L44) + +*** + +### source + +```ts +source: SubscribeSource; +``` + +Defined in: [Subscribe.ts:43](https://github.com/TanStack/table/blob/main/packages/preact-table/src/Subscribe.ts#L43) diff --git a/docs/framework/preact/reference/type-aliases/SubscribePropsWithSourceWithSelector.md b/docs/framework/preact/reference/type-aliases/SubscribePropsWithSourceWithSelector.md new file mode 100644 index 0000000000..0afc4423b8 --- /dev/null +++ b/docs/framework/preact/reference/type-aliases/SubscribePropsWithSourceWithSelector.md @@ -0,0 +1,64 @@ +--- +id: SubscribePropsWithSourceWithSelector +title: SubscribePropsWithSourceWithSelector +--- + +# Type Alias: SubscribePropsWithSourceWithSelector\ + +```ts +type SubscribePropsWithSourceWithSelector = object; +``` + +Defined in: [Subscribe.ts:51](https://github.com/TanStack/table/blob/main/packages/preact-table/src/Subscribe.ts#L51) + +Subscribe to a projected value from a source (atom or store). + +## Type Parameters + +### TSourceValue + +`TSourceValue` + +### TSelected + +`TSelected` + +## Properties + +### children + +```ts +children: (state) => ComponentChildren | ComponentChildren; +``` + +Defined in: [Subscribe.ts:54](https://github.com/TanStack/table/blob/main/packages/preact-table/src/Subscribe.ts#L54) + +*** + +### selector() + +```ts +selector: (state) => TSelected; +``` + +Defined in: [Subscribe.ts:53](https://github.com/TanStack/table/blob/main/packages/preact-table/src/Subscribe.ts#L53) + +#### Parameters + +##### state + +`TSourceValue` + +#### Returns + +`TSelected` + +*** + +### source + +```ts +source: SubscribeSource; +``` + +Defined in: [Subscribe.ts:52](https://github.com/TanStack/table/blob/main/packages/preact-table/src/Subscribe.ts#L52) diff --git a/docs/framework/preact/reference/type-aliases/SubscribePropsWithStore.md b/docs/framework/preact/reference/type-aliases/SubscribePropsWithStore.md new file mode 100644 index 0000000000..c5f743aa3e --- /dev/null +++ b/docs/framework/preact/reference/type-aliases/SubscribePropsWithStore.md @@ -0,0 +1,71 @@ +--- +id: SubscribePropsWithStore +title: SubscribePropsWithStore +--- + +# Type Alias: SubscribePropsWithStore\ + +```ts +type SubscribePropsWithStore = object; +``` + +Defined in: [Subscribe.ts:21](https://github.com/TanStack/table/blob/main/packages/preact-table/src/Subscribe.ts#L21) + +Subscribe to `table.store` (full table state). The selector receives the full +TableState. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TSelected + +`TSelected` + +## Properties + +### children + +```ts +children: (state) => ComponentChildren | ComponentChildren; +``` + +Defined in: [Subscribe.ts:34](https://github.com/TanStack/table/blob/main/packages/preact-table/src/Subscribe.ts#L34) + +*** + +### selector() + +```ts +selector: (state) => TSelected; +``` + +Defined in: [Subscribe.ts:33](https://github.com/TanStack/table/blob/main/packages/preact-table/src/Subscribe.ts#L33) + +Select from full table state. Re-renders when the selected value changes +(shallow compare). + +Required in store mode so you never accidentally subscribe to the whole +store without an explicit projection. + +#### Parameters + +##### state + +`TableState`\<`TFeatures`\> + +#### Returns + +`TSelected` + +*** + +### source + +```ts +source: SubscribeSource>; +``` + +Defined in: [Subscribe.ts:25](https://github.com/TanStack/table/blob/main/packages/preact-table/src/Subscribe.ts#L25) diff --git a/docs/framework/preact/reference/type-aliases/SubscribeSource.md b/docs/framework/preact/reference/type-aliases/SubscribeSource.md new file mode 100644 index 0000000000..4541094988 --- /dev/null +++ b/docs/framework/preact/reference/type-aliases/SubscribeSource.md @@ -0,0 +1,22 @@ +--- +id: SubscribeSource +title: SubscribeSource +--- + +# Type Alias: SubscribeSource\ + +```ts +type SubscribeSource = + | Atom + | ReadonlyAtom + | Store +| ReadonlyStore; +``` + +Defined in: [Subscribe.ts:11](https://github.com/TanStack/table/blob/main/packages/preact-table/src/Subscribe.ts#L11) + +## Type Parameters + +### TValue + +`TValue` diff --git a/docs/framework/qwik/guide/table-state.md b/docs/framework/qwik/guide/table-state.md deleted file mode 100644 index fcc42631c2..0000000000 --- a/docs/framework/qwik/guide/table-state.md +++ /dev/null @@ -1,178 +0,0 @@ ---- -title: Table State (Qwik) Guide ---- - -## Table State (Qwik) Guide - -TanStack Table has a simple underlying internal state management system to store and manage the state of the table. It also lets you selectively pull out any state that you need to manage in your own state management. This guide will walk you through the different ways in which you can interact with and manage the state of the table. - -### Accessing Table State - -You do not need to set up anything special in order for the table state to work. If you pass nothing into either `state`, `initialState`, or any of the `on[State]Change` table options, the table will manage its own state internally. You can access any part of this internal state by using the `table.getState()` table instance API. - -```jsx -const table = useQwikTable({ - columns, - data, - //... -}) - -console.log(table.getState()) //access the entire internal state -console.log(table.getState().rowSelection) //access just the row selection state -``` - -### Custom Initial State - -If all you need to do for certain states is customize their initial default values, you still do not need to manage any of the state yourself. You can simply set values in the `initialState` option of the table instance. - -```jsx -const table = useQwikTable({ - columns, - data, - initialState: { - columnOrder: ['age', 'firstName', 'lastName'], //customize the initial column order - columnVisibility: { - id: false //hide the id column by default - }, - expanded: true, //expand all rows by default - sorting: [ - { - id: 'age', - desc: true //sort by age in descending order by default - } - ] - }, - //... -}) -``` - -> **Note**: Only specify each particular state in either `initialState` or `state`, but not both. If you pass in a particular state value to both `initialState` and `state`, the initialized state in `state` will take overwrite any corresponding value in `initialState`. - -### Controlled State - -If you need easy access to the table state in other areas of your application, TanStack Table makes it easy to control and manage any or all of the table state in your own state management system. You can do this by passing in your own state and state management functions to the `state` and `on[State]Change` table options. - -#### Individual Controlled State - -You can control just the state that you need easy access to. You do NOT have to control all of the table state if you do not need to. It is recommended to only control the state that you need on a case-by-case basis. - -In order to control a particular state, you need to both pass in the corresponding `state` value and the `on[State]Change` function to the table instance. - -Let's take filtering, sorting, and pagination as an example in a "manual" server-side data fetching scenario. You can store the filtering, sorting, and pagination state in your own state management, but leave out any other state like column order, column visibility, etc. if your API does not care about those values. - -```jsx -const columnFilters = Qwik.useSignal([]) //no default filters -const sorting = Qwik.useSignal([{ - id: 'age', - desc: true, //sort by age in descending order by default -}]) -const pagination = Qwik.useSignal({ pageIndex: 0, pageSize: 15 }) - -//Use our controlled state values to fetch data -const tableQuery = useQuery({ - queryKey: ['users', columnFilters.value, sorting.value, pagination.value], - queryFn: () => fetchUsers(columnFilters.value, sorting.value, pagination.value), - //... -}) - -const table = useQwikTable({ - columns: columns.value, - data: tableQuery.data, - //... - state: { - columnFilters: columnFilters.value, //pass controlled state back to the table (overrides internal state) - sorting: sorting.value, - pagination: pagination.value, - }, - onColumnFiltersChange: updater => { - columnFilters.value = updater instanceof Function ? updater(columnFilters.value) : updater //hoist columnFilters state into our own state management - }, - onSortingChange: updater => { - sorting.value = updater instanceof Function ? updater(sorting.value) : updater - }, - onPaginationChange: updater => { - pagination.value = updater instanceof Function ? updater(pagination.value) : updater - }, -}) -//... -``` - -#### Fully Controlled State - -Alternatively, you can control the entire table state with the `onStateChange` table option. It will hoist out the entire table state into your own state management system. Be careful with this approach, as you might find that raising some frequently changing state values up a component tree, like `columnSizingInfo` state`, might cause bad performance issues. - -A couple of more tricks may be needed to make this work. If you use the `onStateChange` table option, the initial values of the `state` must be populated with all of the relevant state values for all of the features that you want to use. You can either manually type out all of the initial state values, or use the `table.setOptions` API in a special way as shown below. - -```jsx -//create a table instance with default state values -const table = useQwikTable({ - columns, - data, - //... Note: `state` values are NOT passed in yet -}) - - -const sate = Qwik.useSignal({ - ...table.initialState, //populate the initial state with all of the default state values from the table instance - pagination: { - pageIndex: 0, - pageSize: 15 //optionally customize the initial pagination state. - } -}) - -//Use the table.setOptions API to merge our fully controlled state onto the table instance -table.setOptions(prev => ({ - ...prev, //preserve any other options that we have set up above - state: state.value, //our fully controlled state overrides the internal state - onStateChange: updater => { - state.value = updater instanceof Function ? updater(state.value) : updater //any state changes will be pushed up to our own state management - }, -})) -``` - -### On State Change Callbacks - -So far, we have seen the `on[State]Change` and `onStateChange` table options work to "hoist" the table state changes into our own state management. However, there are a few things about these using these options that you should be aware of. - -#### 1. **State Change Callbacks MUST have their corresponding state value in the `state` option**. - -Specifying an `on[State]Change` callback tells the table instance that this will be a controlled state. If you do not specify the corresponding `state` value, that state will be "frozen" with its initial value. - -```jsx -const sorting = Qwik.useSignal([]) -//... -const table = useQwikTable({ - columns, - data, - //... - state: { - sorting: sorting.value, //required because we are using `onSortingChange` - }, - onSortingChange: updater => { - sorting.value = updater instanceof Function ? updater(sorting) : updater //makes the `state.sorting` controlled - }, -}) -``` - -#### 2. **Updaters can either be raw values or callback functions**. - -The `on[State]Change` and `onStateChange` callbacks work exactly like the `setState` functions in React. The updater values can either be a new state value or a callback function that takes the previous state value and returns the new state value. - -What implications does this have? It means that if you want to add in some extra logic in any of the `on[State]Change` callbacks, you can do so, but you need to check whether or not the new incoming updater value is a function or value. - -This is why you will see the `updater instanceof Function ? updater(state.value) : updater` pattern in the examples above. This pattern checks if the updater is a function, and if it is, it calls the function with the previous state value to get the new state value. - -### State Types - -All complex states in TanStack Table have their own TypeScript types that you can import and use. This can be handy for ensuring that you are using the correct data structures and properties for the state values that you are controlling. - -```tsx -import { useQwikTable, type SortingState } from '@tanstack/qwik-table' -//... -const sorting = Qwik.useSignal([ - { - id: 'age', //you should get autocomplete for the `id` and `desc` properties - desc: true, - } -]) -``` \ No newline at end of file diff --git a/docs/framework/qwik/qwik-table.md b/docs/framework/qwik/qwik-table.md deleted file mode 100644 index d7e1ade8e6..0000000000 --- a/docs/framework/qwik/qwik-table.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -title: Qwik Table ---- - -The `@tanstack/qwik-table` adapter is a wrapper around the core table logic. Most of it's job is related to managing state the "qwik" way, providing types and the rendering implementation of cell/header/footer templates. - -## Exports - -`@tanstack/qwik-table` re-exports all of `@tanstack/table-core`'s APIs and the following: - -### `useQwikTable` - -Takes an `options` object and returns a table from a Qwik Store with `NoSerialize`. - -```ts -import { useQwikTable } from '@tanstack/qwik-table' - -const table = useQwikTable(options) -// ...render your table - -``` - -### `flexRender` - -A utility function for rendering cell/header/footer templates with dynamic values. - -Example: - -```jsx -import { flexRender } from '@tanstack/qwik-table' -//... -return ( - - {table.getRowModel().rows.map(row => { - return ( - - {row.getVisibleCells().map(cell => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ))} - - ) - })} - -); -``` diff --git a/docs/framework/react/guide/create-table-hook.md b/docs/framework/react/guide/create-table-hook.md new file mode 100644 index 0000000000..254b26daa0 --- /dev/null +++ b/docs/framework/react/guide/create-table-hook.md @@ -0,0 +1,281 @@ +--- +title: createTableHook Guide +--- + +`createTableHook` is an advanced API for building reusable, composable table configurations. It lets you define features, row models, and pre-bound components once, then reuse them across multiple tables with minimal boilerplate. It is inspired by [TanStack Form's `createFormHook`](https://tanstack.com/form/latest/docs/framework/react/guides/form-composition). + +> **When to use it:** Use `createTableHook` when you have multiple tables that share the same configuration (features, row models, and reusable components). For a single table, `useTable` is sufficient. + +## Examples + +- [Composable Tables](../examples/composable-tables) — Two tables (Users and Products) sharing the same `createTableHook` configuration, with table/cell/header components, sorting, filtering, and pagination. +- [Basic useAppTable](../examples/basic-use-app-table) — Minimal example using `createTableHook` with no pre-bound components. + +## Setup + +Create a shared table configuration file and call `createTableHook` with your features, row models, and component registries: + +```tsx +// hooks/table.ts + +import { + createTableHook, + tableFeatures, + columnFilteringFeature, + rowPaginationFeature, + rowSortingFeature, + createFilteredRowModel, + createPaginatedRowModel, + createSortedRowModel, + filterFns, + sortFns, +} from '@tanstack/react-table' + +import { PaginationControls, RowCount, TableToolbar } from '../components/table-components' +import { TextCell, NumberCell, StatusCell, ProgressCell } from '../components/cell-components' +import { SortIndicator, ColumnFilter } from '../components/header-components' + +export const { + createAppColumnHelper, + useAppTable, + useTableContext, + useCellContext, + useHeaderContext, +} = createTableHook({ + _features: tableFeatures({ + columnFilteringFeature, + rowPaginationFeature, + rowSortingFeature, + }), + + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + }, + + getRowId: (row) => row.id, + + tableComponents: { + PaginationControls, + RowCount, + TableToolbar, + }, + + cellComponents: { + TextCell, + NumberCell, + StatusCell, + ProgressCell, + }, + + headerComponents: { + SortIndicator, + ColumnFilter, + }, +}) +``` + +## What `createTableHook` Returns + +| Export | Description | +|--------|-------------| +| `useAppTable` | Hook for creating tables. Merges default options from the hook with per-table options. No need to pass `_features` or `_rowModels`—they come from the hook. | +| `createAppColumnHelper` | Column helper with `TFeatures` pre-bound. Only requires `TData`. Use `createAppColumnHelper()` instead of `createColumnHelper()`. | +| `useTableContext` | Access the table instance inside `tableComponents`. | +| `useCellContext` | Access the cell instance inside `cellComponents`. | +| `useHeaderContext` | Access the header instance inside `headerComponents`. | + +## Component Registries + +### `tableComponents` + +Components that need access to the **table instance**. They are attached to the table object, so you use them as `table.PaginationControls`, `table.RowCount`, etc. + +Use `useTableContext()` inside these components: + +```tsx +export function PaginationControls() { + const table = useTableContext() + + return ( +
+ + +
+ ) +} +``` + +### `cellComponents` + +Components that render **cell content**. They are attached to the `cell` object in column definitions, so you use them as `cell.TextCell`, `cell.NumberCell`, etc. + +Use `useCellContext()` inside these components: + +```tsx +export function TextCell() { + const cell = useCellContext() + return {cell.getValue()} +} + +export function NumberCell() { + const cell = useCellContext() + return {cell.getValue().toLocaleString()} +} +``` + +### `headerComponents` + +Components that render **header/footer content**. They are attached to the `header` object, so you use them as `header.SortIndicator`, `header.ColumnFilter`, etc. + +Use `useHeaderContext()` inside these components: + +```tsx +export function SortIndicator() { + const header = useHeaderContext() + const sorted = header.column.getIsSorted() + if (!sorted) return null + return {sorted === 'asc' ? '🔼' : '🔽'} +} +``` + +## Using `useAppTable` + +Create tables with `useAppTable`—`_features` and `_rowModels` are inherited from the hook: + +```tsx +const personColumnHelper = createAppColumnHelper() + +function UsersTable() { + const [data, setData] = useState(() => makeData(1000)) + + const columns = useMemo( + () => + personColumnHelper.columns([ + personColumnHelper.accessor('firstName', { + header: 'First Name', + cell: ({ cell }) => , + }), + personColumnHelper.accessor('age', { + header: 'Age', + cell: ({ cell }) => , + }), + personColumnHelper.accessor('status', { + header: 'Status', + cell: ({ cell }) => , + }), + ]), + [], + ) + + const table = useAppTable({ + columns, + data, + debugTable: true, + }) + + return ( + ({ pagination: state.pagination, sorting: state.sorting })}> + {({ sorting }) => ( +
+ setData(makeData(1000))} /> + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((h) => ( + + {(header) => ( + + )} + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getAllCells().map((c) => ( + + {(cell) => } + + ))} + + ))} + +
+ + + +
+ + +
+ )} +
+ ) +} +``` + +## AppTable, AppHeader, AppCell, AppFooter + +The table returned by `useAppTable` includes wrapper components that provide context to your registered components: + +- **`table.AppTable`** — Wraps the table UI and provides a `selector` prop for optimized re-renders. Renders its children with the selected state. +- **`table.AppHeader`** — Wraps a header and provides the enhanced `header` context (with `header.SortIndicator`, `header.ColumnFilter`, etc.) to its render prop. +- **`table.AppCell`** — Wraps a cell and provides the enhanced `cell` context (with `cell.TextCell`, `cell.FlexRender`, etc.) to its render prop. +- **`table.AppFooter`** — Same as AppHeader but for footer cells. + +## Optimized Rendering with `selector` + +Pass a `selector` to `table.AppTable` to subscribe only to the state slices you need. This reduces re-renders when other state (e.g., column filters) changes but your component doesn't use it: + +```tsx + ({ + pagination: state.pagination, + sorting: state.sorting, + columnFilters: state.columnFilters, + })} +> + {({ sorting, columnFilters }) => ( + // This only re-renders when pagination, sorting, or columnFilters change +
...
+ )} +
+``` + +For v8-style behavior (re-render on any state change), pass `(state) => state`. + +## Multiple Table Configurations + +You can call `createTableHook` multiple times for different parts of your app: + +```tsx +// admin-tables.ts +export const { useAppTable: useAdminTable, createAppColumnHelper: createAdminColumnHelper } = + createTableHook({ + _features: tableFeatures({ rowSortingFeature, columnFilteringFeature, rowSelectionFeature }), + _rowModels: { /* ... */ }, + cellComponents: { EditableCell, DeleteButton }, + }) + +// readonly-tables.ts +export const { useAppTable: useReadonlyTable, createAppColumnHelper: createReadonlyColumnHelper } = + createTableHook({ + _features: tableFeatures({ rowSortingFeature }), + _rowModels: { /* ... */ }, + cellComponents: { TextCell, NumberCell }, + }) +``` + +## See Also + +- [Migrating to v9](./migrating) — Includes a createTableHook section +- [Composable Tables example](../examples/composable-tables) — Full implementation with two tables diff --git a/docs/framework/react/guide/migrating.md b/docs/framework/react/guide/migrating.md new file mode 100644 index 0000000000..092dcfa231 --- /dev/null +++ b/docs/framework/react/guide/migrating.md @@ -0,0 +1,1043 @@ +--- +title: Migrating to TanStack Table v9 (React) +--- + +## What's New in TanStack Table v9 + +TanStack Table v9 is a major release that introduces significant architectural improvements while maintaining the core table logic you're familiar with. Here are the key changes: + +### 1. Tree-shaking + +- **Features are tree-shakeable**: Features are now treated as plugins—import only what you use. If your table only needs sorting, you won't ship filtering, pagination, or other feature code. Bundlers can eliminate unused code, so for smaller tables you can expect to bundle ~6–7kb compared to 15–20kb for the same table in v8. This also lets TanStack Table add more features over time without bloating everyone's bundles. +- **Row models and their functions are refactored**: Row model factories (`createFilteredRowModel`, `createSortedRowModel`, etc.) now accept their processing functions (`filterFns`, `sortFns`, `aggregationFns`) as parameters. This enables tree-shaking of the functions themselves—if you use a custom filter, you don't pay for built-in filters you never use. + +### 2. State Management + +- **Uses TanStack Store**: The internal state system has been rebuilt on [TanStack Store](https://tanstack.com/store), providing a reactive, framework-agnostic foundation. This works similarly to TanStack Form's state model. +- **Three-layer atom architecture**: Each state slice (sorting, pagination, rowSelection, etc.) lives in its own [atom](https://tanstack.com/store/latest/docs/reference/atom) rather than a single monolithic state object. Internally, the library writes to per-slice `baseAtoms`; reads go through derived `table.atoms` and the flat `table.store`. This enables fine-grained reactivity — components can subscribe to just the slices they care about. +- **Opt-in subscriptions instead of memo hacks**: Use `table.Subscribe` or pass a selector to `useTable` to subscribe to specific slices of state. Only re-render when the state you care about changes—no more `React.memo` or manual memoization. Pass `state => state` if you want v8-style behavior where any state change triggers a re-render. +- **Bring your own atoms (optional)**: For advanced use cases, you can own individual state slices by passing your own writable atoms via the new `atoms` option. This is great for sharing a slice across components or integrating with other atom-based tools. Precedence: `options.atoms[key]` > `options.state[key]` > internal `baseAtoms[key]`. + +### 3. Composability + +- **`tableOptions`**: New utilities let you compose and share table configurations. Define `_features`, `_rowModels`, and default options once, then reuse them across tables or pass them through `createTableHook`. +- **`createTableHook`** (optional, advanced): Create custom table hooks with pre-bound features, row models, and components—similar to TanStack Form's `createFormHook`. Define your table setup once and reuse it across many tables. You don't need this for most use cases; `useTable` is sufficient. + +### The Good News: Most Upgrades Are Opt-in + +While v9 is a significant upgrade, **you don't have to adopt everything at once**: + +- **Don't want to optimize renders?** Pass `state => state` as the selector to `useTable` and rendering works like v8. +- **Don't want to think about tree-shaking?** Import `stockFeatures` to include all features, just like v8. +- **Table markup is largely unchanged.** How you render ``, ``, ``, ` + {table.getRowModel().rows.map((row) => ( + ... + ))} + + )} + +) +``` + +You can also subscribe directly to a single atom and select one value from it: + +```tsx + rowSelection[row.id]} +> + {(isSelected) => ( + + )} + +``` + +Advanced subscription patterns require understanding which table APIs depend on which state slices. For example, a row model may depend on filtering, sorting, and pagination, while one row selection checkbox may only need one row's selection value. Start with the default selector, then optimize with selectors and `table.Subscribe` where render cost matters. + +### Setting Table State + +You should almost never need to set table state directly. TanStack Table features expose dedicated APIs for interacting with their state, and those APIs are the safest way to make changes because they preserve the feature's own rules and related updates. + +For example, use pagination APIs instead of mutating pagination state yourself: + +```tsx +table.nextPage() +table.previousPage() +table.setPageIndex(0) +table.setPageSize(25) ``` +The same idea applies across features. Use APIs like `table.setSorting(...)`, `table.setColumnFilters(...)`, `column.toggleVisibility()`, or `row.toggleSelected()` instead of manually editing the underlying state object. + +If you only care about setting starting values, use `initialState`. If you want to reset a state slice back to its initial value, use that feature's reset API. + +If you really do need to write a state slice directly, the low-level write surface for internally owned state is the matching base atom: + +```tsx +table.baseAtoms.pagination.set((old) => ({ + ...old, + pageIndex: 0, +})) +``` + +Direct base atom writes should be rare. If a slice is owned by an external atom passed through `atoms`, write to that external atom instead; `table.atoms.pagination` will read from the external atom, not the internal base atom. + ### Custom Initial State -If all you need to do for certain states is customize their initial default values, you still do not need to manage any of the state yourself. You can simply set values in the `initialState` option of the table instance. +If you only need to customize the starting value for some table state, use `initialState`. You still do not need to manage that state yourself. -```jsx -const table = useReactTable({ +`initialState` only applies to registered state slices. It is used to create the table's initial state and is also used by reset APIs such as `table.resetSorting()` or `table.resetPagination()`. Changing the `initialState` object later does not reset table state. + +```tsx +const table = useTable({ + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, columns, data, initialState: { - columnOrder: ['age', 'firstName', 'lastName'], //customize the initial column order - columnVisibility: { - id: false //hide the id column by default - }, - expanded: true, //expand all rows by default sorting: [ { id: 'age', - desc: true //sort by age in descending order by default - } - ] + desc: true, + }, + ], + pagination: { + pageIndex: 0, + pageSize: 25, + }, }, - //... }) ``` -> **Note**: Only specify each particular state in either `initialState` or `state`, but not both. If you pass in a particular state value to both `initialState` and `state`, the initialized state in `state` will take overwrite any corresponding value in `initialState`. +> **Note:** Do not provide the same state slice in multiple ownership places unless you intentionally want one to win. For a slice like `pagination`, prefer exactly one of `initialState.pagination`, `atoms.pagination`, or `state.pagination` as the source of truth. External atoms take precedence over external `state`; external `state` syncs into the table's internal base atom. + +#### Resetting to Initial State + +Feature reset APIs reset to `table.initialState` by default. Many reset APIs also accept `true` to reset to that feature's blank/default state instead: + +```tsx +table.resetSorting() +table.resetPagination() +table.resetPagination(true) +``` + +Slice reset APIs like `resetPagination()` update through that feature's state updater and can update an externally owned atom. The core `table.reset()` API resets the internal base atoms, so do not use it as the primary way to reset state that is owned by external atoms. ### Controlled State -If you need easy access to the table state in other areas of your application, TanStack Table makes it easy to control and manage any or all of the table state in your own state management system. You can do this by passing in your own state and state management functions to the `state` and `on[State]Change` table options. +If you need easy access to table state in other parts of your application, you can control individual state slices. In v9, external atoms are the recommended way to do this because they preserve the atomic state model and let React subscribe to exactly the slices it needs. -#### Individual Controlled State +#### External Atoms -You can control just the state that you need easy access to. You do NOT have to control all of the table state if you do not need to. It is recommended to only control the state that you need on a case-by-case basis. +Use external atoms when the app should own one or more table state slices. Create stable writable atoms with `useCreateAtom` from TanStack Store, pass them to the table's `atoms` option, and subscribe to them with `useSelector` anywhere else in your app. -In order to control a particular state, you need to both pass in the corresponding `state` value and the `on[State]Change` function to the table instance. +This is especially useful for server-side data fetching. Pagination, sorting, or filters often belong in a query key, and external atoms let the app and the table share those values without lifting the entire table state through React state. -Let's take filtering, sorting, and pagination as an example in a "manual" server-side data fetching scenario. You can store the filtering, sorting, and pagination state in your own state management, but leave out any other state like column order, column visibility, etc. if your API does not care about those values. +```tsx +import { useCreateAtom, useSelector } from '@tanstack/react-store' +import { + rowPaginationFeature, + tableFeatures, + useTable, + type PaginationState, +} from '@tanstack/react-table' + +const _features = tableFeatures({ + rowPaginationFeature, +}) -```jsx -const [columnFilters, setColumnFilters] = React.useState([]) //no default filters -const [sorting, setSorting] = React.useState([{ - id: 'age', - desc: true, //sort by age in descending order by default -}]) -const [pagination, setPagination] = React.useState({ pageIndex: 0, pageSize: 15 }) +function App() { + const paginationAtom = useCreateAtom({ + pageIndex: 0, + pageSize: 10, + }) + + const pagination = useSelector(paginationAtom) + + const dataQuery = useQuery({ + queryKey: ['data', pagination], + queryFn: () => fetchData(pagination), + }) + + const table = useTable({ + _features, + _rowModels: {}, + columns, + data: dataQuery.data?.rows ?? [], + rowCount: dataQuery.data?.rowCount, + atoms: { + pagination: paginationAtom, + }, + manualPagination: true, + }) + + // table pagination APIs update paginationAtom +} +``` -//Use our controlled state values to fetch data -const tableQuery = useQuery({ - queryKey: ['users', columnFilters, sorting, pagination], - queryFn: () => fetchUsers(columnFilters, sorting, pagination), - //... +When using the `atoms` option for a slice, you do not need to add the matching `on[State]Change` option. For example, if you pass `atoms.pagination`, table pagination APIs update that atom directly. + +#### External State + +The classic `state` plus `on[State]Change` pattern is still supported. This can be convenient for simple integrations or when migrating v8 code, but it is less fine-grained than external atoms. React state updates re-render the owner component, and that render cannot be avoided by the `useTable` selector in the same way atom subscriptions can be placed lower in the tree. + +To control a slice with external state, pass both the state value and the matching callback: + +```tsx +const [sorting, setSorting] = React.useState([]) +const [pagination, setPagination] = React.useState({ + pageIndex: 0, + pageSize: 10, }) -const table = useReactTable({ +const table = useTable({ + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, columns, - data: tableQuery.data, - //... + data, state: { - columnFilters, //pass controlled state back to the table (overrides internal state) sorting, - pagination + pagination, }, - onColumnFiltersChange: setColumnFilters, //hoist columnFilters state into our own state management onSortingChange: setSorting, onPaginationChange: setPagination, }) -//... ``` -#### Fully Controlled State +The v8-style `onStateChange` option is no longer part of the v9 `useTable` state model. It remains available through `useLegacyTable` for migration, but v9 encourages keeping table state slices atomic and separated for performance. -Alternatively, you can control the entire table state with the `onStateChange` table option. It will hoist out the entire table state into your own state management system. Be careful with this approach, as you might find that raising some frequently changing state values up a react tree, like `columnSizingInfo` state`, might cause bad performance issues. +##### On State Change Callbacks -A couple of more tricks may be needed to make this work. If you use the `onStateChange` table option, the initial values of the `state` must be populated with all of the relevant state values for all of the features that you want to use. You can either manually type out all of the initial state values, or use the `table.setOptions` API in a special way as shown below. +The `on[State]Change` callbacks are useful when you are controlling a matching slice through the `state` option. They work like React state setters: an updater can be a raw value or a function that receives the previous value and returns the next value. -```jsx -//create a table instance with default state values -const table = useReactTable({ - columns, - data, - //... Note: `state` values are NOT passed in yet -}) +If you provide an `on[State]Change` callback, also provide the corresponding value in `state`. For example, `onSortingChange` should be paired with `state.sorting`. +```tsx +const [sorting, setSorting] = React.useState([]) -const [state, setState] = React.useState({ - ...table.initialState, //populate the initial state with all of the default state values from the table instance - pagination: { - pageIndex: 0, - pageSize: 15 //optionally customize the initial pagination state. - } -}) - -//Use the table.setOptions API to merge our fully controlled state onto the table instance -table.setOptions(prev => ({ - ...prev, //preserve any other options that we have set up above - state, //our fully controlled state overrides the internal state - onStateChange: setState //any state changes will be pushed up to our own state management -})) -``` - -### On State Change Callbacks - -So far, we have seen the `on[State]Change` and `onStateChange` table options work to "hoist" the table state changes into our own state management. However, there are a few things about using these options that you should be aware of. - -#### 1. **State Change Callbacks MUST have their corresponding state value in the `state` option**. - -Specifying an `on[State]Change` callback tells the table instance that this will be a controlled state. If you do not specify the corresponding `state` value, that state will be "frozen" with its initial value. - -```jsx -const [sorting, setSorting] = React.useState([]) -//... -const table = useReactTable({ +const table = useTable({ + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + }, columns, data, - //... state: { - sorting, //required because we are using `onSortingChange` + sorting, }, - onSortingChange: setSorting, //makes the `state.sorting` controlled + onSortingChange: setSorting, }) ``` -#### 2. **Updaters can either be raw values or callback functions**. +If you need to add logic inside a callback, check whether the incoming updater is a function or a value: -The `on[State]Change` and `onStateChange` callbacks work exactly like the `setState` functions in React. The updater values can either be a new state value or a callback function that takes the previous state value and returns the new state value. - -What implications does this have? It means that if you want to add in some extra logic in any of the `on[State]Change` callbacks, you can do so, but you need to check whether or not the new incoming updater value is a function or value. - -```jsx -const [sorting, setSorting] = React.useState([]) -const [pagination, setPagination] = React.useState({ pageIndex: 0, pageSize: 10 }) +```tsx +const [pagination, setPagination] = React.useState({ + pageIndex: 0, + pageSize: 10, +}) -const table = useReactTable({ +const table = useTable({ + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + }, columns, data, - //... state: { pagination, - sorting, - } - //syntax 1 + }, onPaginationChange: (updater) => { - setPagination(old => { - const newPaginationValue = updater instanceof Function ? updater(old) : updater - //do something with the new pagination value - //... - return newPaginationValue + setPagination((old) => { + const next = updater instanceof Function ? updater(old) : updater + + // side effects or validation can happen here + + return next }) }, - //syntax 2 - onSortingChange: (updater) => { - const newSortingValue = updater instanceof Function ? updater(sorting) : updater - //do something with the new sorting value - //... - setSorting(updater) //normal state update - } }) ``` ### State Types -All complex states in TanStack Table have their own TypeScript types that you can import and use. This can be handy for ensuring that you are using the correct data structures and properties for the state values that you are controlling. +Most complex states in TanStack Table have their own TypeScript types that you can import and use. This is useful for React state, external atoms, and helper functions that work with table state. ```tsx -import { useReactTable, type SortingState } from '@tanstack/react-table' -//... -const [sorting, setSorting] = React.useState([ +import { + useTable, + type PaginationState, + type RowSelectionState, + type SortingState, + type TableState, +} from '@tanstack/react-table' + +const [sorting, setSorting] = React.useState([ { - id: 'age', //you should get autocomplete for the `id` and `desc` properties + id: 'age', desc: true, - } + }, ]) ``` + +`TableState` is inferred from the features registered on that table: + +```tsx +const _features = tableFeatures({ + rowPaginationFeature, + rowSortingFeature, +}) + +type MyTableState = TableState +``` + +Prefer feature-specific state types like `SortingState`, `PaginationState`, or `RowSelectionState` when you are creating local state or external atoms for one slice. diff --git a/docs/framework/react/guide/use-legacy-table.md b/docs/framework/react/guide/use-legacy-table.md new file mode 100644 index 0000000000..ce5dd0ef96 --- /dev/null +++ b/docs/framework/react/guide/use-legacy-table.md @@ -0,0 +1,226 @@ +--- +title: Using useLegacyTable for Incremental Migration +--- + +The `useLegacyTable` hook provides a compatibility layer that accepts the v8-style API while using v9 under the hood. This is useful for teams that need to migrate incrementally or have large codebases where a full migration isn't immediately practical. + +> **Warning:** `useLegacyTable` is **deprecated** and intended only as a temporary migration aid. It includes all features by default, resulting in a larger bundle size compared to the tree-shakeable v9 API. Plan to migrate to `useTable` for better performance and smaller bundles. + +## When to Use `useLegacyTable` + +- You have an existing v8 codebase and need to upgrade dependencies +- You want to migrate incrementally, one table at a time +- You need v9 compatibility but don't have time for a full migration yet + +## Basic Usage + +```tsx +import { useState } from 'react' +import { flexRender } from '@tanstack/react-table' +import { + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + legacyCreateColumnHelper, + useLegacyTable, +} from '@tanstack/react-table/legacy' +import type { + ColumnFiltersState, + PaginationState, + SortingState, +} from '@tanstack/react-table' +import type { + LegacyColumn, + LegacyColumnDef, + LegacyRow, +} from '@tanstack/react-table/legacy' + +interface Person { + name: string + email: string + age: number +} + +const columnHelper = legacyCreateColumnHelper() + +const columns: LegacyColumnDef[] = [ + columnHelper.accessor('name', { header: 'Name' }), + columnHelper.accessor('email', { header: 'Email' }), + columnHelper.accessor('age', { header: 'Age' }), + columnHelper.display({ id: 'actions', header: 'Actions' }), +] + +function MyTable({ data }: { data: Person[] }) { + const [sorting, setSorting] = useState([]) + const [columnFilters, setColumnFilters] = useState([]) + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 10, + }) + + // useLegacyTable accepts the v8-style API + const table = useLegacyTable({ + columns, + data, + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getSortedRowModel: getSortedRowModel(), + getPaginationRowModel: getPaginationRowModel(), + state: { sorting, columnFilters, pagination }, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + onPaginationChange: setPagination, + }) + + return ( +
`, etc. remains the same. + +The main change is **how you define a table** with the `useTable` hook — specifically the new `_features` and `_rowModels` options. + +--- + +## Quick Legacy Migration + +Need to migrate incrementally? Use `useLegacyTable` — it accepts the v8-style API while using v9 under the hood. **This is deprecated** and intended only as a temporary migration aid. It includes all features by default, resulting in a larger bundle size. + +Legacy APIs live in a separate export. Import core utilities from `@tanstack/react-table` and legacy-specific APIs from `@tanstack/react-table/legacy`: + +```tsx +import { flexRender } from '@tanstack/react-table' +import { + useLegacyTable, + getCoreRowModel, + getFilteredRowModel, + getSortedRowModel, + getPaginationRowModel, + legacyCreateColumnHelper, +} from '@tanstack/react-table/legacy' +``` + +See the [useLegacyTable Guide](./use-legacy-table.md) for full documentation, examples, and type helpers. + +--- + +The rest of this guide focuses on migrating to the full v9 API and taking advantage of its features. + +## Core Breaking Changes + +### Hook Rename + +The hook name has been simplified to be consistent across all TanStack libraries: + +```tsx +// v8 +import { useReactTable } from '@tanstack/react-table' +const table = useReactTable(options) + +// v9 +import { useTable } from '@tanstack/react-table' +const table = useTable(options) +``` + +### New Required Options: `_features` and `_rowModels` + +In v9, you must explicitly declare which features and row models your table uses: + +```tsx +// v8 +import { useReactTable, getCoreRowModel } from '@tanstack/react-table' + +const table = useReactTable({ + columns, + data, + getCoreRowModel: getCoreRowModel(), +}) + +// v9 +import { useTable, tableFeatures } from '@tanstack/react-table' + +const _features = tableFeatures({}) // Empty = core features only + +const table = useTable({ + _features, + _rowModels: {}, // Core row model is automatic + columns, + data, +}) +``` + +--- + +## The `_features` Option + +Features control what table functionality is available. In v8, all features were bundled. In v9, you import only what you need. + +### Importing Individual Features + +```tsx +import { + tableFeatures, + // Import only the features you need + columnFilteringFeature, + rowSortingFeature, + rowPaginationFeature, + columnVisibilityFeature, + rowSelectionFeature, +} from '@tanstack/react-table' + +// Create a features object (define this outside your component for stable reference) +const _features = tableFeatures({ + columnFilteringFeature, + rowSortingFeature, + rowPaginationFeature, + columnVisibilityFeature, + rowSelectionFeature, +}) +``` + +### Using `stockFeatures` for v8-like Behavior + +If you want all features without thinking about it (like v8), import `stockFeatures`: + +```tsx +import { useTable, stockFeatures } from '@tanstack/react-table' + +const table = useTable({ + _features: stockFeatures, // All features included + _rowModels: { /* ... */ }, + columns, + data, +}) +``` + +### Available Features + +| Feature | Import Name | +|---------|-------------| +| Column Filtering | `columnFilteringFeature` | +| Global Filtering | `globalFilteringFeature` | +| Row Sorting | `rowSortingFeature` | +| Row Pagination | `rowPaginationFeature` | +| Row Selection | `rowSelectionFeature` | +| Row Expanding | `rowExpandingFeature` | +| Row Pinning | `rowPinningFeature` | +| Column Pinning | `columnPinningFeature` | +| Column Visibility | `columnVisibilityFeature` | +| Column Ordering | `columnOrderingFeature` | +| Column Sizing | `columnSizingFeature` | +| Column Resizing | `columnResizingFeature` | +| Column Grouping | `columnGroupingFeature` | +| Column Faceting | `columnFacetingFeature` | + +--- + +## The `_rowModels` Option + +Row models are the functions that process your data (filtering, sorting, pagination, etc.). In v9, they're configured via `_rowModels` instead of `get*RowModel` options. + +### Migration Mapping + +| v8 Option | v9 `_rowModels` Key | v9 Factory Function | +|-----------|---------------------|---------------------| +| `getCoreRowModel()` | (automatic) | Not needed — always included | +| `getFilteredRowModel()` | `filteredRowModel` | `createFilteredRowModel(filterFns)` | +| `getSortedRowModel()` | `sortedRowModel` | `createSortedRowModel(sortFns)` | +| `getPaginationRowModel()` | `paginatedRowModel` | `createPaginatedRowModel()` | +| `getExpandedRowModel()` | `expandedRowModel` | `createExpandedRowModel()` | +| `getGroupedRowModel()` | `groupedRowModel` | `createGroupedRowModel(aggregationFns)` | +| `getFacetedRowModel()` | `facetedRowModel` | `createFacetedRowModel()` | +| `getFacetedMinMaxValues()` | `facetedMinMaxValues` | `createFacetedMinMaxValues()` | +| `getFacetedUniqueValues()` | `facetedUniqueValues` | `createFacetedUniqueValues()` | + +### Key Change: Row Model Functions Now Accept Parameters + +Several row model factories now accept their processing functions as parameters. This enables better tree-shaking and explicit configuration: + +```tsx +import { + createFilteredRowModel, + createSortedRowModel, + createGroupedRowModel, + filterFns, // Built-in filter functions + sortFns, // Built-in sort functions + aggregationFns, // Built-in aggregation functions +} from '@tanstack/react-table' + +const table = useTable({ + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + sortedRowModel: createSortedRowModel(sortFns), + groupedRowModel: createGroupedRowModel(aggregationFns), + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data, +}) +``` + +### Full Migration Example + +```tsx +// v8 +import { + useReactTable, + getCoreRowModel, + getFilteredRowModel, + getSortedRowModel, + getPaginationRowModel, + filterFns, + sortingFns, +} from '@tanstack/react-table' + +const table = useReactTable({ + columns, + data, + getCoreRowModel: getCoreRowModel(), // used to be called "get*RowModel()" + getFilteredRowModel: getFilteredRowModel(), + getSortedRowModel: getSortedRowModel(), + getPaginationRowModel: getPaginationRowModel(), + filterFns, // used to be passed in as a root option + sortingFns, +}) + +// v9 +import { + useTable, + tableFeatures, + columnFilteringFeature, + rowSortingFeature, + rowPaginationFeature, + createFilteredRowModel, + createSortedRowModel, + createPaginatedRowModel, + filterFns, + sortFns, +} from '@tanstack/react-table' + +const _features = tableFeatures({ + columnFilteringFeature, + rowSortingFeature, + rowPaginationFeature, +}) + +const table = useTable({ + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), // now called "create*RowModel()" with a Fns parameter + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data, +}) +``` + +--- + +## State Management Changes + +v9's state system is built on [TanStack Store](https://tanstack.com/store) and exposes three read surfaces on the table instance: + +| Surface | Type | When to use | +|---------|------|-------------| +| `table.state` | `TSelected` (the shape you return from your `useTable` selector) | The most ergonomic read surface inside a component rendered by `useTable`. | +| `table.store` | `ReadonlyStore` | A flat, framework-agnostic store of the entire table state. Use `table.store.state` for one-off reads, or pair with `useSelector` / `table.Subscribe` for fine-grained subscriptions. | +| `table.atoms.` | `ReadonlyAtom` | A per-slice readonly atom. Subscribe to a single slice (e.g. `table.atoms.sorting`) when you want the narrowest possible re-render surface. | + +Writable counterparts (mostly internal): + +| Surface | Type | When to use | +|---------|------|-------------| +| `table.baseAtoms.` | `Atom` | The library's internal write target. You generally don't touch these directly — use `table.setSorting(...)`, `table.setPagination(...)`, etc. | +| `options.atoms` | `Partial<{ [slice]: Atom }>` | Pass in your own writable atom for any slice to take ownership of that state externally. See [External Atoms](#external-atoms-advanced) below. | + +### Accessing State + +In v8, you accessed state via `table.getState()`. In v9, state is accessed differently: + +```tsx +// v8 +const state = table.getState() +const { sorting, pagination } = table.getState() + +// v9 - via the store (full state) +const fullState = table.store.state +const { sorting, pagination } = table.store.state + +// v9 - via table.state (selected state from your selector) +const table = useTable(options, (state) => ({ + sorting: state.sorting, + pagination: state.pagination, +})) +// Now table.state only contains sorting and pagination +const { sorting, pagination } = table.state + +// v9 - via a single slice atom (framework-agnostic, ideal for fine-grained subscriptions) +const sorting = table.atoms.sorting.get() +``` + +### Optimized Rendering with `table.Subscribe` + +The biggest state management improvement is `table.Subscribe`, which enables fine-grained reactivity: + +```tsx +function MyTable() { + const table = useTable({ + _features, + _rowModels: { /* ... */ }, + columns, + data, + }) + + return ( + ({ + sorting: state.sorting, + pagination: state.pagination, + })} + > + {({ sorting, pagination }) => ( + // This only re-renders when sorting or pagination changes +
+ {/* ... */}
+
Page {pagination.pageIndex + 1}
+
+ )} +
+ ) +} +``` + +### Opt-Out: v8-Style Full State Subscription + +If you want v8-style behavior where the component re-renders on any state change, pass `state => state` as the selector: + +```tsx +// Re-renders on ANY state change (like v8) +const table = useTable( + { + _features, + _rowModels: { /* ... */ }, + columns, + data, + }, + (state) => state, // Subscribe to entire state +) + +// table.state now contains the full state +const { sorting, pagination, columnFilters } = table.state +``` + +### Controlled State + +Controlled state patterns work similarly to v8: + +```tsx +const [sorting, setSorting] = useState([]) +const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 10, +}) + +const table = useTable({ + _features, + _rowModels: { /* ... */ }, + columns, + data, + state: { + sorting, + pagination, + }, + onSortingChange: setSorting, + onPaginationChange: setPagination, +}) +``` + +### Per-Slice Atom Subscriptions + +Because each state slice is backed by its own atom, you can subscribe a component to a single slice without re-rendering on any other state change. Use `useSelector` from `@tanstack/react-store` with `table.atoms.`: + +```tsx +import { useSelector } from '@tanstack/react-store' + +function PaginationFooter({ table }) { + // Re-renders only when pagination changes — sorting, filtering, selection, etc. are all ignored. + const pagination = useSelector(table.atoms.pagination) + + return
Page {pagination.pageIndex + 1}
+} +``` + +This is the narrowest subscription surface available. Compared to `table.Subscribe`, which selects from the full `table.store.state`, reading a per-slice atom skips even constructing the full state snapshot on change. + +> **When to reach for `table.atoms` vs. `table.Subscribe`:** Both give you fine-grained re-renders. `table.Subscribe` is nicer when you want to project multiple slices into a single rendered block. `table.atoms.` is nicer when a component only cares about one slice, or when you're passing a subscription source to non-table code. + +### External Atoms (Advanced) + +For advanced patterns — sharing a slice across tables, integrating with atom-based libraries, or wiring a slice up to persistence — v9 lets you **own individual state slices yourself** by passing writable atoms via the new `atoms` option. See the [Basic External Atoms example](../examples/basic-external-atoms). + +```tsx +import { useCreateAtom, useSelector } from '@tanstack/react-store' +import { + useTable, + tableFeatures, + rowSortingFeature, + rowPaginationFeature, + createSortedRowModel, + createPaginatedRowModel, + sortFns, +} from '@tanstack/react-table' +import type { PaginationState, SortingState } from '@tanstack/react-table' + +const _features = tableFeatures({ rowSortingFeature, rowPaginationFeature }) + +function MyTable({ data, columns }) { + // Create stable external atoms for the slices you want to own. + const sortingAtom = useCreateAtom([]) + const paginationAtom = useCreateAtom({ + pageIndex: 0, + pageSize: 10, + }) + + // Subscribe to each atom independently — fine-grained reactivity. + const sorting = useSelector(sortingAtom) + const pagination = useSelector(paginationAtom) + + const table = useTable({ + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data, + // Per-slice external atoms — the library writes directly to these, + // bypassing the internal baseAtoms for those slices. + atoms: { + sorting: sortingAtom, + pagination: paginationAtom, + }, + }) + + // Table writes like table.setPageIndex(2) go straight to `paginationAtom`. + // Any other subscriber of `paginationAtom` will see the update too. + // ... +} +``` + +#### How External Atoms Interact with `state` and `on*Change` + +When you register an external atom for a slice: + +- **Reads**: The derived `table.atoms[slice]` and `table.store.state[slice]` both read from your external atom. +- **Writes**: Library writes (e.g. `table.setSorting(...)`, `column.toggleSorting()`) go directly to your external atom's `set()`. You do **not** need a corresponding `onSortingChange` handler — owning the atom is the subscription. +- **Precedence**: If you pass both `options.atoms[key]` and `options.state[key]`, the atom wins. If you pass neither, v9 falls back to its internal `baseAtoms[key]` (v8-style self-managed state). +- **Reset**: `table.reset()` does **not** clear external atoms — you own them, so you decide when to reset. Call `myAtom.set(defaultValue)` yourself if needed. + +#### When to Choose External Atoms vs. Controlled State + +| Pattern | Use when | +|---------|----------| +| Internal state (no `state`, no `atoms`) | Simplest path; the table manages everything. | +| `state` + `on*Change` (v8-style controlled state) | You want your framework's idiomatic state (React `useState`, signals, etc.) to own the slice. | +| `atoms` option | You want atom-based ergonomics (cross-component subscriptions, `useSelector`, `useAtom`) without the overhead of mirroring between React state and the table. | + +--- + +## Column Helper Changes + +The `createColumnHelper` function now requires a `TFeatures` type parameter in addition to `TData`: + +```tsx +// v8 +import { createColumnHelper } from '@tanstack/react-table' + +const columnHelper = createColumnHelper() + +// v9 +import { createColumnHelper, tableFeatures, rowSortingFeature } from '@tanstack/react-table' + +const _features = tableFeatures({ rowSortingFeature }) +const columnHelper = createColumnHelper() +``` + +### New `columns()` Helper Method + +v9 adds a `columns()` helper for better type inference when wrapping column arrays. In v8, `TValue` wasn't always type-safe—especially with group columns, where nested column types could be lost or widened. The `columns()` helper uses variadic tuple types to preserve each column's individual `TValue` type, so `info.getValue()` and cell renderers stay correctly typed throughout nested structures: + +```tsx +const columnHelper = createColumnHelper() + +// Wrap your columns array for better type inference +const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('lastName', { + id: 'lastName', + header: () => Last Name, + cell: (info) => {info.getValue()}, + }), + columnHelper.display({ + id: 'actions', + header: 'Actions', + cell: (info) => , + }), +]) +``` + +### Using with `createTableHook` + +When using `createTableHook`, you get a pre-bound `createAppColumnHelper` that only requires `TData`: + +```tsx +const { useAppTable, createAppColumnHelper } = createTableHook({ + _features: tableFeatures({ rowSortingFeature }), + _rowModels: { /* ... */ }, +}) + +// TFeatures is already bound — only need TData! +const columnHelper = createAppColumnHelper() +``` + +--- + +## Rendering Changes + +### `flexRender` Function + +The `flexRender` function still exists and works the same way: + +```tsx +import { flexRender } from '@tanstack/react-table' + +// Still works in v9 +{flexRender(cell.column.columnDef.cell, cell.getContext())} +{flexRender(header.column.columnDef.header, header.getContext())} +``` + +### New `` Component + +v9 adds a cleaner component-based approach attached to the table instance: + +```tsx +const table = useTable({ /* ... */ }) + +// Instead of: +{flexRender(header.column.columnDef.header, header.getContext())} + +// You can use: + + + +``` + +This should be way more convenient and type-safe than the old `flexRender` function! + +### Standalone `` Component + +There's also a standalone component you can import: + +```tsx +import { FlexRender } from '@tanstack/react-table' + + + + +``` + +--- + +## The `tableOptions()` Utility + +The `tableOptions()` helper provides type-safe composition of table options. It's useful for creating reusable partial configurations that can be spread into your table setup. + +### Basic Usage + +```tsx +import { tableOptions, tableFeatures, rowSortingFeature } from '@tanstack/react-table' + +// Create a reusable options object with features pre-configured +const baseOptions = tableOptions({ + _features: tableFeatures({ rowSortingFeature }), + debugTable: process.env.NODE_ENV === 'development', +}) + +// Use in your table — columns, data, and other options can be added +const table = useTable({ + ...baseOptions, + columns, + data, + _rowModels: {}, +}) +``` + +### Composing Partial Options + +`tableOptions()` allows you to omit certain required fields (like `data`, `columns`, or `_features`) when creating partial configurations: + +```tsx +// Partial options without data or columns +const featureOptions = tableOptions({ + _features: tableFeatures({ + rowSortingFeature, + columnFilteringFeature, + }), + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + filteredRowModel: createFilteredRowModel(filterFns), + }, +}) + +// Another partial without _features (inherits from spread) +const paginationDefaults = tableOptions({ + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + }, + initialState: { + pagination: { pageIndex: 0, pageSize: 25 }, + }, +}) + +// Combine them +const table = useTable({ + ...featureOptions, + ...paginationDefaults, + columns, + data, +}) +``` + +### Using with `createTableHook` + +`tableOptions()` pairs well with `createTableHook` for building composable table factories: + +```tsx +const sharedOptions = tableOptions({ + _features: tableFeatures({ rowSortingFeature, rowPaginationFeature }), + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, +}) + +const { useAppTable } = createTableHook(sharedOptions) +``` + +--- + +## `createTableHook`: Composable Table Patterns + +**This is an advanced, optional feature.** You don't need to use `createTableHook`—`useTable` is sufficient for most use cases. If you're familiar with [TanStack Form](https://tanstack.com/form)'s `createFormHook`, `createTableHook` works almost the same way: it creates a custom hook with pre-bound configuration that you can reuse across many tables. + +For applications with multiple tables sharing the same configuration, `createTableHook` lets you define features, row models, and reusable components once: + +```tsx +// hooks/table.ts +import { + createTableHook, + tableFeatures, + columnFilteringFeature, + rowSortingFeature, + rowPaginationFeature, + createFilteredRowModel, + createSortedRowModel, + createPaginatedRowModel, + filterFns, + sortFns, +} from '@tanstack/react-table' + +// Import your reusable components +import { PaginationControls, SortIndicator, TextCell } from './components' + +export const { + useAppTable, + createAppColumnHelper, + useTableContext, + useCellContext, + useHeaderContext, +} = createTableHook({ + // Features defined once + _features: tableFeatures({ + columnFilteringFeature, + rowSortingFeature, + rowPaginationFeature, + }), + + // Row models defined once + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, + + // Default table options + debugTable: process.env.NODE_ENV === 'development', + + // Register reusable components + tableComponents: { PaginationControls }, + cellComponents: { TextCell }, + headerComponents: { SortIndicator }, +}) +``` + +### Using `useAppTable` + +```tsx +// features/users.tsx +import { useAppTable, createAppColumnHelper } from './hooks/table' + +const columnHelper = createAppColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + cell: ({ cell }) => , // Pre-bound component! + }), +]) + +function UsersTable({ data }: { data: Person[] }) { + const table = useAppTable({ + columns, + data, + // _features and _rowModels already configured! + }) + + return ( + + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((h) => ( + + {(header) => ( + + )} + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getAllCells().map((c) => ( + + {(cell) => ( + + )} + + ))} + + ))} + +
+ + +
+ +
+ +
+ ) +} +``` + +### Context Hooks for Components + +Components registered via `createTableHook` can access their context: + +```tsx +// components/SortIndicator.tsx +import { useHeaderContext } from './hooks/table' + +export function SortIndicator() { + const header = useHeaderContext() + const sorted = header.column.getIsSorted() + + if (!sorted) return null + return sorted === 'asc' ? ' 🔼' : ' 🔽' +} + +// components/TextCell.tsx +import { useCellContext } from './hooks/table' + +export function TextCell() { + const cell = useCellContext() + return {cell.getValue() as string} +} + +// components/PaginationControls.tsx +import { useTableContext } from './hooks/table' + +export function PaginationControls() { + const table = useTableContext() + + return ( + s.pagination}> + {(pagination) => ( +
+ + Page {pagination.pageIndex + 1} + +
+ )} +
+ ) +} +``` + +--- + +## Other Breaking Changes + +### Column Pinning Option Split + +The `enablePinning` option has been split into separate options: + +```tsx +// v8 +enablePinning: true + +// v9 +enableColumnPinning: true +enableRowPinning: true +``` + +### Removed Internal APIs + +All internal APIs prefixed with `_` have been removed. If you were using any of these, use their public equivalents: + +- Removed: `table._getPinnedRows()` +- Removed: `table._getFacetedRowModel()` +- Removed: `table._getFacetedMinMaxValues()` +- Removed: `table._getFacetedUniqueValues()` + +### Column Sizing vs. Column Resizing Split + +In v8, column sizing and resizing were combined in a single feature. In v9, they've been split into separate features for better tree-shaking. + +| v8 | v9 | +|----|-----| +| `ColumnSizing` (combined feature) | `columnSizingFeature` + `columnResizingFeature` | +| `columnSizingInfo` state | `columnResizing` state | +| `setColumnSizingInfo()` | `setColumnResizing()` | +| `onColumnSizingInfoChange` option | `onColumnResizingChange` option | + +If you only need column sizing (fixed widths) without interactive resizing, you can import just `columnSizingFeature`. If you need drag-to-resize functionality, import both: + +```tsx +import { columnSizingFeature, columnResizingFeature } from '@tanstack/react-table' + +const _features = tableFeatures({ + columnSizingFeature, + columnResizingFeature, // Only if you need interactive resizing +}) +``` + +### Sorting API Renames + +Sorting-related APIs have been renamed for consistency: + +| v8 | v9 | +|----|-----| +| `sortingFn` (column def option) | `sortFn` | +| `column.getSortingFn()` | `column.getSortFn()` | +| `column.getAutoSortingFn()` | `column.getAutoSortFn()` | +| `SortingFn` type | `SortFn` type | +| `SortingFns` interface | `SortFns` interface | +| `sortingFns` (built-in functions) | `sortFns` | + +Update your column definitions: + +```tsx +// v8 +const columns = [ + { + accessorKey: 'name', + sortingFn: 'alphanumeric', // or custom function + }, +] + +// v9 +const columns = [ + { + accessorKey: 'name', + sortFn: 'alphanumeric', // or custom function + }, +] +``` + +### Row API Changes + +Some row APIs have changed from private to public: + +| v8 | v9 | +|----|-----| +| `row._getAllCellsByColumnId()` (private) | `row.getAllCellsByColumnId()` (public) | + +If you were accessing this internal API, you can now use it without the underscore prefix. + +--- + +## TypeScript Changes Summary + +### Type Generics + +Most types now require a `TFeatures` parameter: + +```tsx +// v8 +type Column +type ColumnDef +type Table +type Row +type Cell + +// v9 +type Column +type ColumnDef +type Table +type Row +type Cell +``` + +### Using `typeof _features` + +The easiest way to get the `TFeatures` type is with `typeof`: + +```tsx +const _features = tableFeatures({ + rowSortingFeature, + columnFilteringFeature, +}) + +// Use typeof to get the type +type MyFeatures = typeof _features + +const columns: ColumnDef[] = [...] + +function Filter({ column }: { column: Column }) { + // ... +} +``` + +### Using `StockFeatures` + +If using `stockFeatures` with `useTable`, use the `StockFeatures` type: + +```tsx +import type { StockFeatures, ColumnDef } from '@tanstack/react-table' + +const columns: ColumnDef[] = [...] +``` + +### `ColumnMeta` Generic Change + +If you're using module augmentation to extend `ColumnMeta`, note that it now requires a `TFeatures` parameter: + +```tsx +// v8 +declare module '@tanstack/react-table' { + interface ColumnMeta { + customProperty: string + } +} + +// v9 - TFeatures is now the first parameter +declare module '@tanstack/react-table' { + interface ColumnMeta { + customProperty: string + } +} +``` + +### `RowData` Type Restriction + +The `RowData` type is now more restrictive: + +```tsx +// v8 - very permissive +type RowData = unknown | object | any[] + +// v9 - must be a record or array +type RowData = Record | Array +``` + +This change improves type safety. If you were passing unusual data types, ensure your data conforms to `Record` or `Array`. + +--- + +## Migration Checklist + +- [ ] Update import: `useReactTable` → `useTable` +- [ ] Define `_features` using `tableFeatures()` (or use `stockFeatures`) +- [ ] Migrate `get*RowModel()` options to `_rowModels` +- [ ] Update row model factories to include `Fns` parameters where needed +- [ ] Update TypeScript types to include `TFeatures` generic +- [ ] Update state access: `table.getState()` → `table.store.state` or `table.state` +- [ ] Update `createColumnHelper()` → `createColumnHelper()` +- [ ] Replace `enablePinning` with `enableColumnPinning`/`enableRowPinning` if used +- [ ] Rename `sortingFn` → `sortFn` in column definitions +- [ ] Split column sizing/resizing: use both `columnSizingFeature` and `columnResizingFeature` if needed +- [ ] Rename `columnSizingInfo` state → `columnResizing` (and related options) +- [ ] Update `ColumnMeta` module augmentation to include `TFeatures` generic (if used) +- [ ] (Optional) Add `table.Subscribe` for render optimizations +- [ ] (Optional) Subscribe to individual slices via `table.atoms.` + `useSelector` for the narrowest re-renders +- [ ] (Optional) Pass writable atoms via the new `atoms` option to own specific state slices externally +- [ ] (Optional) Use `tableOptions()` for composable configurations +- [ ] (Optional) Migrate to `createTableHook` for reusable table patterns + +--- + +## Examples + +Check out these examples to see v9 patterns in action: + +- [Basic useTable](../examples/basic-use-table) - Simple table with the new `useTable` hook +- [Basic useLegacyTable](../examples/basic-use-legacy-table) - Migration example using `useLegacyTable` +- [Basic useAppTable](../examples/basic-use-app-table) - Using `createTableHook` +- [Basic External State](../examples/basic-external-state) - Classic `state` + `on*Change` controlled state +- [Basic External Atoms](../examples/basic-external-atoms) - Owning state slices with `useCreateAtom` + the `atoms` option +- [Filters](../examples/filters) - Filtering with the new API +- [Sorting](../examples/sorting) - Sorting with the new API +- [Composable Tables](../examples/composable-tables) - Advanced `createTableHook` patterns diff --git a/docs/framework/react/guide/table-state.md b/docs/framework/react/guide/table-state.md index fb5ac16321..b0b744ec78 100644 --- a/docs/framework/react/guide/table-state.md +++ b/docs/framework/react/guide/table-state.md @@ -6,199 +6,433 @@ title: Table State (React) Guide Want to skip to the implementation? Check out these examples: -- [kitchen sink](../../examples/kitchen-sink) -- [fully controlled](../../examples/fully-controlled) +- [Basic useTable](../examples/basic-use-table) +- [Basic External Atoms](../examples/basic-external-atoms) +- [Basic External State](../examples/basic-external-state) +- [Basic Subscribe](../examples/basic-subscribe) +- [With TanStack Query](../examples/with-tanstack-query) ## Table State (React) Guide -TanStack Table has a simple underlying internal state management system to store and manage the state of the table. It also lets you selectively pull out any state that you need to manage in your own state management. This guide will walk you through the different ways in which you can interact with and manage the state of the table. +> **If you boil TanStack Table down to one sentence: TanStack Table is a large state-management coordinator for table states.** -### Accessing Table State +Understanding this guide is fundamental to understanding how TanStack Table works and how to interact with it for the best results. + +### Do you need to Manage External State? + +You usually do NOT need to manage table state yourself. If you pass nothing to `initialState`, `atoms`, `state`, or any of the `on[State]Change` table options, TanStack Table will manage its own state internally. + +There will be situations where you need to customize how you interact with the internal table state, or even hoist it up to your own scopes. TanStack Table lets you read, subscribe to, or own the state slices that matter to your app. This guide explains how table state works in React, how to read it, and when to use external atoms or external state. + +### State in v9 + +TanStack Table v9 overhauled state management around TanStack Store. TanStack Store uses the `alien-signals` implementation and supports performant derived state. For TanStack Table, this means the table can derive a full state store from independent state atoms and React can subscribe to only the pieces of table state that a component actually needs. + +A table instance has a few state surfaces: + +- `table.baseAtoms` are the internal writable atoms created from the resolved initial state. +- `table.atoms` are readonly derived atoms exposed per registered state slice. +- `table.store` is a readonly flat TanStack Store derived by putting all of the registered `table.atoms` together. +- `table.state` is React-only selected state. It is the value returned from the selector passed as the second argument to `useTable`. + +The important change from previous versions is that table state is now atomic. React can subscribe to all selected state, a selected subset of state, or a single atom such as `table.atoms.rowSelection`. -You do not need to set up anything special in order for the table state to work. If you pass nothing into either `state`, `initialState`, or any of the `on[State]Change` table options, the table will manage its own state internally. You can access any part of this internal state by using the `table.getState()` table instance API. +### Feature-based State -```jsx -const table = useReactTable({ +State slices are only created for the features that are registered in `_features`. This keeps TanStack Table tree-shakeable and gives TypeScript more accurate state inference. + +For example, if `_features` includes `rowPaginationFeature`, TypeScript exposes pagination state APIs and `table.atoms.pagination`. If `_features` does not include `rowPaginationFeature`, `pagination` should not be available in `table.atoms`, `table.store.state`, `table.state`, `initialState`, `state`, or `atoms`. + +```tsx +const _features = tableFeatures({ + rowPaginationFeature, + rowSortingFeature, +}) + +const table = useTable({ + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, columns, data, - //... }) -console.log(table.getState()) //access the entire internal state -console.log(table.getState().rowSelection) //access just the row selection state +table.atoms.pagination.get() +table.atoms.sorting.get() + +// table.atoms.rowSelection // TypeScript error unless rowSelectionFeature is registered +``` + +The same feature-based typing applies to built-in features and custom feature-provided state. + +### Accessing Table State + +There are two different questions when reading table state: + +- Do you only need the current value? +- Or should this React component re-render when that value changes? + +Use a direct atom or store read for the current value. Use a selector subscription for reactive rendering. + +#### Reading State Without Subscribing + +The simplest and most performant way to read a current state value is to read the matching atom: + +```tsx +const pagination = table.atoms.pagination.get() +const sorting = table.atoms.sorting.get() +``` + +You can also read the current flat store snapshot: + +```tsx +const tableState = table.store.state +const pagination = table.store.state.pagination +``` + +These reads are not React subscriptions. Calling `table.atoms.pagination.get()` or `table.store.state.pagination` during render reads the current value, but future changes will not automatically re-render that component unless something else causes a render. If the UI needs to stay reactive to table state changes, use `useTable` state selection, `table.Subscribe`, or even a `useSelector` hook from TanStack Store. + +#### Reading Reactive State with useTable + +The second argument to `useTable` is a TanStack Store selector. By default, the selector effectively selects all registered table state, so `table.state` contains the full state and the component re-renders when any selected state changes. + +You can pass your own selector to make `table.state` contain only the reactive state values that you want to cause re-renders. The React adapter compares selected values shallowly. The default selector selects all registered table state. + +```tsx +const table = useTable( + { + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data, + }, + (state) => ({ + pagination: state.pagination, + }), +) + +table.state.pagination +``` + +For large tables, you can also opt the parent table component out of table-state re-renders and subscribe lower in the tree: + +```tsx +const table = useTable( + { + _features, + _rowModels: {}, + columns, + data, + }, + () => null, +) +``` + +With this pattern, the parent component will not re-render for table state changes. Put reactive reads inside `table.Subscribe` where the UI actually needs them. + +#### Optimizing Re-renders with Selectors and table.Subscribe + +Use `table.Subscribe` when you want table-state re-renders to happen at a specific place in the React tree. This is useful for large or expensive tables, but it is usually something to reach for after the default `useTable` selector becomes a visible performance issue. + +Without a `source` prop, `table.Subscribe` subscribes to `table.store` and requires a selector. With a `source` prop, it can subscribe directly to one atom or store, such as `table.atoms.rowSelection`. + +```tsx +const table = useTable( + { + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data, + }, + () => null, +) + +return ( + ({ + columnFilters: state.columnFilters, + globalFilter: state.globalFilter, + pagination: state.pagination, + })} + > + {() => ( +
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getAllCells().map((cell) => ( + + ))} + + ))} + +
+ {flexRender( + header.column.columnDef.header, + header.getContext() + )} + {header.column.getCanFilter() ? ( + + ) : null} +
+ {cell.column.id === 'actions' ? ( + + ) : ( + flexRender(cell.column.columnDef.cell, cell.getContext()) + )} +
+ ) +} + +function Filter({ column }: { column: LegacyColumn }) { + return ( + column.setFilterValue(e.target.value)} + placeholder="Filter..." + /> + ) +} + +function RowActions({ row }: { row: LegacyRow }) { + return +} +``` + +## Type Helpers + +When using `useLegacyTable`, use these type helpers for proper TypeScript support: + +| Type | Description | +|------|-------------| +| `LegacyColumnDef` | Column definition type (equivalent to v8's `ColumnDef`) | +| `LegacyColumn` | Column instance type | +| `LegacyRow` | Row instance type | +| `LegacyCell` | Cell instance type | +| `LegacyTable` | Table instance type | +| `legacyCreateColumnHelper()` | Column helper with StockFeatures pre-bound—only requires TData | + +### Using `legacyCreateColumnHelper` + +Use `legacyCreateColumnHelper` instead of `createColumnHelper`—it has StockFeatures pre-bound, so you only need to specify `TData`: + +```tsx +import { legacyCreateColumnHelper } from '@tanstack/react-table/legacy' +import type { LegacyColumnDef } from '@tanstack/react-table/legacy' + +const columnHelper = legacyCreateColumnHelper() + +const columns: LegacyColumnDef[] = [ + columnHelper.accessor('name', { header: 'Name' }), + // ... +] +``` + +## API Differences from v8 + +While `useLegacyTable` aims for v8 compatibility, there are a few differences: + +### Row Model Functions + +The `get*RowModel()` functions are imported from `@tanstack/react-table/legacy`: + +```tsx +import { + getCoreRowModel, + getFilteredRowModel, + getSortedRowModel, + getPaginationRowModel, + getExpandedRowModel, + getGroupedRowModel, + getFacetedRowModel, + getFacetedMinMaxValues, + getFacetedUniqueValues, +} from '@tanstack/react-table/legacy' +``` + +### Sorting Function Renames + +Note that in v9, sorting-related APIs have been renamed. If you're using custom sorting functions in column definitions: + +| v8 | v9 | +|----|-----| +| `sortingFn` | `sortFn` | + +The legacy table adapter handles this internally for built-in sorting, but if you're defining custom sorting functions, be aware of the rename. + +## Caveats and Limitations + +### Bundle Size + +`useLegacyTable` includes **all features** by default, similar to v8. This means: + +- No tree-shaking benefits +- Bundle size is **much larger** than v8—each feature has grown since v8, and you pay for all of them +- The tree-shakeable v9 API exists so TanStack Table can add features over time without bloating everyone's bundles; only users who opt into a feature pay for it +- If bundle size is a concern, prioritize migrating to the full v9 API + +### Deprecation + +`useLegacyTable` is deprecated and will be removed in a future major version. It exists solely to ease migration. Plan your migration timeline accordingly. + +### No `table.Subscribe` + +The fine-grained reactivity feature (`table.Subscribe`) is not available with `useLegacyTable`. The table re-renders on every state change, like v8. + +### No `createTableHook` Integration + +`useLegacyTable` cannot be used with `createTableHook`. If you want to create reusable table configurations, migrate to the full v9 API. + +## Migration Path + +Once you're ready to migrate to the full v9 API: + +1. Replace `useLegacyTable` with `useTable` +2. Define your `_features` using `tableFeatures()` +3. Convert `get*RowModel()` options to `_rowModels` +4. Update types from `Legacy*` to the standard v9 types + +See the [main migration guide](./migrating.md) for complete instructions. + +## Example + +See the [Basic useLegacyTable example](../examples/basic-use-legacy-table) for a working implementation. diff --git a/docs/framework/react/react-table.md b/docs/framework/react/react-table.md index 2dd6834c83..92a553d872 100644 --- a/docs/framework/react/react-table.md +++ b/docs/framework/react/react-table.md @@ -2,18 +2,156 @@ title: React Table --- -The `@tanstack/react-table` adapter is a wrapper around the core table logic. Most of its job is related to managing state the "react" way, providing types and the rendering implementation of cell/header/footer templates. +The `@tanstack/react-table` adapter wraps `@tanstack/table-core` with React-specific reactivity, rendering helpers, and types. It installs the React `coreReativityFeature` for you, so table state is backed by TanStack Store atoms while React components can subscribe through `useTable`, selectors, and `table.Subscribe`. -## `useReactTable` +TanStack Table v9 is explicit about what a table uses. Register features with `_features`, and register client-side row model factories with `_rowModels`. The core row model is included by default, so a basic table can use `_rowModels: {}`. -Takes an `options` object and returns a table. +## Creating a Table + +Use `useTable` to create a React table instance. + +```tsx +import { tableFeatures, useTable, type ColumnDef } from '@tanstack/react-table' + +type Person = { + firstName: string + lastName: string + age: number +} + +const _features = tableFeatures({}) + +const columns: Array> = [ + { + accessorKey: 'firstName', + header: 'First name', + cell: (info) => info.getValue(), + }, +] + +function App({ data }: { data: Person[] }) { + const table = useTable({ + _features, + _rowModels: {}, + columns, + data, + }) + + return null +} +``` + +For feature-specific row models, register the feature and put the row model factory under `_rowModels`. + +```tsx +import { + createPaginatedRowModel, + createSortedRowModel, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/react-table' + +const _features = tableFeatures({ + rowPaginationFeature, + rowSortingFeature, +}) + +const tableOptions = { + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, +} +``` + +## Table State + +Table state is managed with TanStack Store atoms in v9. For most tables, you do not need to manage table state yourself: set `initialState` when you need starting values, and use feature APIs like `table.nextPage()`, `table.setSorting(...)`, and `row.toggleSelected()` instead of mutating state directly. + +Use `atoms` when your app should own one state slice with TanStack Store. Use `state` with the matching `on[State]Change` option for simple React state integration or migration paths. ```tsx -import { useReactTable } from '@tanstack/react-table' +import { useCreateAtom } from '@tanstack/react-store' +import { + rowPaginationFeature, + tableFeatures, + useTable, + type PaginationState, +} from '@tanstack/react-table' -function App() { - const table = useReactTable(options) +const _features = tableFeatures({ + rowPaginationFeature, +}) - // ...render your table +function App({ data }: { data: Person[] }) { + const paginationAtom = useCreateAtom({ + pageIndex: 0, + pageSize: 10, + }) + + const table = useTable({ + _features, + _rowModels: {}, + columns, + data, + atoms: { + pagination: paginationAtom, + }, + }) + + return null } ``` + +For reactive reads, the second argument to `useTable` selects from `table.store` and exposes the result on `table.state`. For large tables, `table.Subscribe` can subscribe smaller parts of the UI to selected state or individual atoms. See the [Table State Guide](./guide/table-state.md) and the [Basic Subscribe example](./examples/basic-subscribe). + +## Rendering Headers, Cells, and Footers + +Use `table.FlexRender` to render column `header`, `cell`, and `footer` definitions. It handles plain values and React components. + +```tsx + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + + + ))} + + ))} + +``` + +## createTableHook + +`createTableHook` creates an app-specific table hook. Use it when multiple tables should share `_features`, `_rowModels`, default options, column helpers, and component conventions. + +```tsx +import { createTableHook, tableFeatures } from '@tanstack/react-table' + +const { useAppTable, createAppColumnHelper } = createTableHook({ + _features: tableFeatures({}), + _rowModels: {}, +}) + +const columnHelper = createAppColumnHelper() + +function App({ data }: { data: Person[] }) { + const table = useAppTable({ + columns, + data, + }) + + return null +} +``` + +See the [createTableHook Guide](./guide/create-table-hook.md) and the [Composable Tables example](./examples/composable-tables) for the full pattern. + +## API Reference + +See the [React API Reference](./reference/index.md). diff --git a/docs/framework/react/reference/index.md b/docs/framework/react/reference/index.md new file mode 100644 index 0000000000..1902741d6c --- /dev/null +++ b/docs/framework/react/reference/index.md @@ -0,0 +1,11 @@ +--- +id: "@tanstack/react-table" +title: "@tanstack/react-table" +--- + +# @tanstack/react-table + +## Modules + +- [index](index/index.md) +- [legacy](legacy/index.md) diff --git a/docs/framework/react/reference/index/functions/FlexRender-1.md b/docs/framework/react/reference/index/functions/FlexRender-1.md new file mode 100644 index 0000000000..1fc66ee6b0 --- /dev/null +++ b/docs/framework/react/reference/index/functions/FlexRender-1.md @@ -0,0 +1,71 @@ +--- +id: FlexRender +title: FlexRender +--- + +# Function: FlexRender() + +```ts +function FlexRender(props): + | string + | number + | bigint + | boolean + | Iterable + | Promise + | Element + | null + | undefined; +``` + +Defined in: [FlexRender.tsx:97](https://github.com/TanStack/table/blob/main/packages/react-table/src/FlexRender.tsx#L97) + +Simplified component wrapper of `flexRender`. Use this utility component to render headers, cells, or footers with custom markup. +Only one prop (`cell`, `header`, or `footer`) may be passed. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### props + +[`FlexRenderProps`](../type-aliases/FlexRenderProps.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + + \| `string` + \| `number` + \| `bigint` + \| `boolean` + \| `Iterable`\<`ReactNode`, `any`, `any`\> + \| `Promise`\<`AwaitedReactNode`\> + \| `Element` + \| `null` + \| `undefined` + +## Example + +```tsx + + + +``` + +This replaces calling `flexRender` directly like this: +```tsx +flexRender(cell.column.columnDef.cell, cell.getContext()) +flexRender(header.column.columnDef.header, header.getContext()) +flexRender(footer.column.columnDef.footer, footer.getContext()) +``` diff --git a/docs/framework/react/reference/index/functions/Subscribe.md b/docs/framework/react/reference/index/functions/Subscribe.md new file mode 100644 index 0000000000..c8f099b527 --- /dev/null +++ b/docs/framework/react/reference/index/functions/Subscribe.md @@ -0,0 +1,218 @@ +--- +id: Subscribe +title: Subscribe +--- + +# Function: Subscribe() + +## Call Signature + +```ts +function Subscribe(props): ReactNode | Promise; +``` + +Defined in: [Subscribe.ts:125](https://github.com/TanStack/table/blob/main/packages/react-table/src/Subscribe.ts#L125) + +A React component that allows you to subscribe to the table state. + +This is useful for opting into state re-renders for specific parts of the table state. + +For `table.Subscribe` from `useTable`, prefer that API — it uses overloads so JSX +contextual typing works. This standalone component uses a union `props` type. + +### Type Parameters + +#### TSourceValue + +`TSourceValue` + +### Parameters + +#### props + +[`SubscribePropsWithSourceIdentity`](../type-aliases/SubscribePropsWithSourceIdentity.md)\<`TSourceValue`\> + +### Returns + +`ReactNode` \| `Promise`\<`ReactNode`\> + +### Examples + +```tsx +// As a standalone component — full store + ({ rowSelection: state.rowSelection })}> + {({ rowSelection }) => ( +
Selected rows: {Object.keys(rowSelection).length}
+ )} +
+``` + +```tsx +// Entire source (atom or store) — no selector + + {(rowSelection) =>
...
} +
+``` + +```tsx +// Project source value (e.g. one row’s selection) + rowSelection?.[row.id]} +> + {(selected) => ...} + +``` + +```tsx +// As table.Subscribe (table instance method) + ({ rowSelection: state.rowSelection })}> + {({ rowSelection }) => ( +
Selected rows: {Object.keys(rowSelection).length}
+ )} +
+``` + +## Call Signature + +```ts +function Subscribe(props): ReactNode | Promise; +``` + +Defined in: [Subscribe.ts:128](https://github.com/TanStack/table/blob/main/packages/react-table/src/Subscribe.ts#L128) + +A React component that allows you to subscribe to the table state. + +This is useful for opting into state re-renders for specific parts of the table state. + +For `table.Subscribe` from `useTable`, prefer that API — it uses overloads so JSX +contextual typing works. This standalone component uses a union `props` type. + +### Type Parameters + +#### TSourceValue + +`TSourceValue` + +#### TSelected + +`TSelected` + +### Parameters + +#### props + +[`SubscribePropsWithSourceWithSelector`](../type-aliases/SubscribePropsWithSourceWithSelector.md)\<`TSourceValue`, `TSelected`\> + +### Returns + +`ReactNode` \| `Promise`\<`ReactNode`\> + +### Examples + +```tsx +// As a standalone component — full store + ({ rowSelection: state.rowSelection })}> + {({ rowSelection }) => ( +
Selected rows: {Object.keys(rowSelection).length}
+ )} +
+``` + +```tsx +// Entire source (atom or store) — no selector + + {(rowSelection) =>
...
} +
+``` + +```tsx +// Project source value (e.g. one row’s selection) + rowSelection?.[row.id]} +> + {(selected) => ...} + +``` + +```tsx +// As table.Subscribe (table instance method) + ({ rowSelection: state.rowSelection })}> + {({ rowSelection }) => ( +
Selected rows: {Object.keys(rowSelection).length}
+ )} +
+``` + +## Call Signature + +```ts +function Subscribe(props): ReactNode | Promise; +``` + +Defined in: [Subscribe.ts:131](https://github.com/TanStack/table/blob/main/packages/react-table/src/Subscribe.ts#L131) + +A React component that allows you to subscribe to the table state. + +This is useful for opting into state re-renders for specific parts of the table state. + +For `table.Subscribe` from `useTable`, prefer that API — it uses overloads so JSX +contextual typing works. This standalone component uses a union `props` type. + +### Type Parameters + +#### TFeatures + +`TFeatures` *extends* `TableFeatures` + +#### TSelected + +`TSelected` + +### Parameters + +#### props + +[`SubscribePropsWithStore`](../type-aliases/SubscribePropsWithStore.md)\<`TFeatures`, `TSelected`\> + +### Returns + +`ReactNode` \| `Promise`\<`ReactNode`\> + +### Examples + +```tsx +// As a standalone component — full store + ({ rowSelection: state.rowSelection })}> + {({ rowSelection }) => ( +
Selected rows: {Object.keys(rowSelection).length}
+ )} +
+``` + +```tsx +// Entire source (atom or store) — no selector + + {(rowSelection) =>
...
} +
+``` + +```tsx +// Project source value (e.g. one row’s selection) + rowSelection?.[row.id]} +> + {(selected) => ...} + +``` + +```tsx +// As table.Subscribe (table instance method) + ({ rowSelection: state.rowSelection })}> + {({ rowSelection }) => ( +
Selected rows: {Object.keys(rowSelection).length}
+ )} +
+``` diff --git a/docs/framework/react/reference/index/functions/createTableHook.md b/docs/framework/react/reference/index/functions/createTableHook.md new file mode 100644 index 0000000000..90220cfbb7 --- /dev/null +++ b/docs/framework/react/reference/index/functions/createTableHook.md @@ -0,0 +1,321 @@ +--- +id: createTableHook +title: createTableHook +--- + +# Function: createTableHook() + +```ts +function createTableHook(__namedParameters): object; +``` + +Defined in: [createTableHook.tsx:590](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L590) + +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 `useAppTable` hook that returns an extended table with App wrapper components +- Get a `createAppColumnHelper` function pre-bound to your features + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TTableComponents + +`TTableComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +## Parameters + +### \_\_namedParameters + +[`CreateTableHookOptions`](../type-aliases/CreateTableHookOptions.md)\<`TFeatures`, `TTableComponents`, `TCellComponents`, `THeaderComponents`\> + +## Returns + +### appFeatures + +```ts +appFeatures: TFeatures; +``` + +### createAppColumnHelper() + +```ts +createAppColumnHelper: () => AppColumnHelper; +``` + +Create a column helper pre-bound to the features and components configured in this table hook. +The cell, header, and footer contexts include pre-bound components (e.g., `cell.TextCell`). + +#### Type Parameters + +##### TData + +`TData` *extends* `RowData` + +#### Returns + +[`AppColumnHelper`](../type-aliases/AppColumnHelper.md)\<`TFeatures`, `TData`, `TCellComponents`, `THeaderComponents`\> + +#### Example + +```tsx +const columnHelper = createAppColumnHelper() + +const columns = [ + columnHelper.accessor('firstName', { + header: 'First Name', + cell: ({ cell }) => , // cell has pre-bound components! + }), + columnHelper.accessor('age', { + header: 'Age', + cell: ({ cell }) => , + }), +] +``` + +### useAppTable() + +```ts +useAppTable: (tableOptions, selector?) => AppReactTable; +``` + +Enhanced useTable hook that returns a table with App wrapper components +and pre-bound tableComponents attached directly to the table object. + +Default options from createTableHook are automatically merged with +the options passed here. Options passed here take precedence. + +TFeatures is already known from the createTableHook call; TData is inferred from the data prop. + +#### Type Parameters + +##### TData + +`TData` *extends* `RowData` + +##### TSelected + +`TSelected` = `TableState`\<`TFeatures`\> + +#### Parameters + +##### tableOptions + +`Omit`\<`TableOptions`\<`TFeatures`, `TData`\>, `"_features"` \| `"_rowModels"`\> + +##### selector? + +(`state`) => `TSelected` + +#### Returns + +[`AppReactTable`](../type-aliases/AppReactTable.md)\<`TFeatures`, `TData`, `TSelected`, `TTableComponents`, `TCellComponents`, `THeaderComponents`\> + +### useCellContext() + +```ts +useCellContext: () => Cell; +``` + +Access the cell instance from within an `AppCell` wrapper. +Use this in custom `cellComponents` passed to `createTableHook`. +TFeatures is already known from the createTableHook call. + +#### Type Parameters + +##### TValue + +`TValue` *extends* `unknown` = `unknown` + +#### Returns + +`Cell`\<`TFeatures`, `any`, `TValue`\> + +#### Example + +```tsx +function TextCell() { + const cell = useCellContext() + return {cell.getValue()} +} + +function NumberCell({ format }: { format?: Intl.NumberFormatOptions }) { + const cell = useCellContext() + return {cell.getValue().toLocaleString(undefined, format)} +} +``` + +### useHeaderContext() + +```ts +useHeaderContext: () => Header; +``` + +Access the header instance from within an `AppHeader` or `AppFooter` wrapper. +Use this in custom `headerComponents` passed to `createTableHook`. +TFeatures is already known from the createTableHook call. + +#### Type Parameters + +##### TValue + +`TValue` *extends* `unknown` = `unknown` + +#### Returns + +`Header`\<`TFeatures`, `any`, `TValue`\> + +#### Example + +```tsx +function SortIndicator() { + const header = useHeaderContext() + const sorted = header.column.getIsSorted() + return sorted === 'asc' ? '🔼' : sorted === 'desc' ? '🔽' : null +} + +function ColumnFilter() { + const header = useHeaderContext() + if (!header.column.getCanFilter()) return null + return ( + header.column.setFilterValue(e.target.value)} + placeholder="Filter..." + /> + ) +} +``` + +### useTableContext() + +```ts +useTableContext: () => ReactTable; +``` + +Access the table instance from within an `AppTable` wrapper. +Use this in custom `tableComponents` passed to `createTableHook`. +TFeatures is already known from the createTableHook call. + +#### Type Parameters + +##### TData + +`TData` *extends* `RowData` = `RowData` + +#### Returns + +[`ReactTable`](../type-aliases/ReactTable.md)\<`TFeatures`, `TData`\> + +#### Example + +```tsx +function PaginationControls() { + const table = useTableContext() + return ( + s.pagination}> + {(pagination) => ( +
+ + Page {pagination.pageIndex + 1} + +
+ )} +
+ ) +} +``` + +## Example + +```tsx +// hooks/table.ts +export const { + useAppTable, + createAppColumnHelper, + useTableContext, + useCellContext, + useHeaderContext, +} = createTableHook({ + _features: tableFeatures({ + rowPaginationFeature, + rowSortingFeature, + columnFilteringFeature, + }), + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + filteredRowModel: createFilteredRowModel(filterFns), + }, + tableComponents: { PaginationControls, RowCount }, + cellComponents: { TextCell, NumberCell }, + headerComponents: { SortIndicator, ColumnFilter }, +}) + +// Create column helper with TFeatures already bound +const columnHelper = createAppColumnHelper() + +// components/table-components.tsx +function PaginationControls() { + const table = useTableContext() // TFeatures already known! + return s.pagination}>... +} + +// features/users.tsx +function UsersTable({ data }: { data: Person[] }) { + const table = useAppTable({ + columns, + data, // TData inferred from Person[] + }) + + return ( + + + + {table.getHeaderGroups().map(headerGroup => ( + + {headerGroup.headers.map(h => ( + + {(header) => ( + + )} + + ))} + + ))} + + + {table.getRowModel().rows.map(row => ( + + {row.getAllCells().map(c => ( + + {(cell) => } + + ))} + + ))} + +
+ + +
+ +
+ ) +} +``` diff --git a/docs/framework/react/reference/index/functions/flexRender.md b/docs/framework/react/reference/index/functions/flexRender.md new file mode 100644 index 0000000000..c2e3d81d19 --- /dev/null +++ b/docs/framework/react/reference/index/functions/flexRender.md @@ -0,0 +1,40 @@ +--- +id: flexRender +title: flexRender +--- + +# Function: flexRender() + +```ts +function flexRender(Comp, props): ReactNode | Element; +``` + +Defined in: [FlexRender.tsx:45](https://github.com/TanStack/table/blob/main/packages/react-table/src/FlexRender.tsx#L45) + +If rendering headers, cells, or footers with custom markup, use flexRender instead of `cell.getValue()` or `cell.renderValue()`. + +## Type Parameters + +### TProps + +`TProps` *extends* `object` + +## Parameters + +### Comp + +[`Renderable`](../type-aliases/Renderable.md)\<`TProps`\> + +### props + +`TProps` + +## Returns + +`ReactNode` \| `Element` + +## Example + +```ts +flexRender(cell.column.columnDef.cell, cell.getContext()) +``` diff --git a/docs/framework/react/reference/index/functions/useTable.md b/docs/framework/react/reference/index/functions/useTable.md new file mode 100644 index 0000000000..439966c38b --- /dev/null +++ b/docs/framework/react/reference/index/functions/useTable.md @@ -0,0 +1,64 @@ +--- +id: useTable +title: useTable +--- + +# Function: useTable() + +```ts +function useTable(tableOptions, selector?): ReactTable; +``` + +Defined in: [useTable.ts:132](https://github.com/TanStack/table/blob/main/packages/react-table/src/useTable.ts#L132) + +Creates a React table instance backed by TanStack Store atoms. + +The optional selector projects from `table.store`; the selected value is +exposed on `table.state` and compared shallowly for React re-renders. Omit +the selector to subscribe to every registered table state slice, or pass a +narrower selector and use `table.Subscribe` lower in the tree for targeted +subscriptions. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TSelected + +`TSelected` = `TableState`\<`TFeatures`\> + +## Parameters + +### tableOptions + +`TableOptions`\<`TFeatures`, `TData`\> + +### selector? + +(`state`) => `TSelected` + +## Returns + +[`ReactTable`](../type-aliases/ReactTable.md)\<`TFeatures`, `TData`, `TSelected`\> + +## Example + +```tsx +const table = useTable( + { + _features, + _rowModels: {}, + columns, + data, + }, + (state) => ({ pagination: state.pagination }), +) + +table.state.pagination +``` diff --git a/docs/framework/react/reference/index/index.md b/docs/framework/react/reference/index/index.md new file mode 100644 index 0000000000..7a3e72dfef --- /dev/null +++ b/docs/framework/react/reference/index/index.md @@ -0,0 +1,47 @@ +--- +id: index +title: index +--- + +# index + +## Interfaces + +- [AppCellComponent](interfaces/AppCellComponent.md) +- [AppCellPropsWithoutSelector](interfaces/AppCellPropsWithoutSelector.md) +- [AppCellPropsWithSelector](interfaces/AppCellPropsWithSelector.md) +- [AppHeaderComponent](interfaces/AppHeaderComponent.md) +- [AppHeaderPropsWithoutSelector](interfaces/AppHeaderPropsWithoutSelector.md) +- [AppHeaderPropsWithSelector](interfaces/AppHeaderPropsWithSelector.md) +- [AppTableComponent](interfaces/AppTableComponent.md) +- [AppTablePropsWithoutSelector](interfaces/AppTablePropsWithoutSelector.md) +- [AppTablePropsWithSelector](interfaces/AppTablePropsWithSelector.md) + +## Type Aliases + +- [AppCellContext](type-aliases/AppCellContext.md) +- [AppColumnDefBase](type-aliases/AppColumnDefBase.md) +- [AppColumnDefTemplate](type-aliases/AppColumnDefTemplate.md) +- [AppColumnHelper](type-aliases/AppColumnHelper.md) +- [AppDisplayColumnDef](type-aliases/AppDisplayColumnDef.md) +- [AppGroupColumnDef](type-aliases/AppGroupColumnDef.md) +- [AppHeaderContext](type-aliases/AppHeaderContext.md) +- [AppReactTable](type-aliases/AppReactTable.md) +- [CreateTableHookOptions](type-aliases/CreateTableHookOptions.md) +- [FlexRenderProps](type-aliases/FlexRenderProps.md) +- [ReactTable](type-aliases/ReactTable.md) +- [Renderable](type-aliases/Renderable.md) +- [SubscribeProps](type-aliases/SubscribeProps.md) +- [SubscribePropsWithSource](type-aliases/SubscribePropsWithSource.md) +- [SubscribePropsWithSourceIdentity](type-aliases/SubscribePropsWithSourceIdentity.md) +- [SubscribePropsWithSourceWithSelector](type-aliases/SubscribePropsWithSourceWithSelector.md) +- [SubscribePropsWithStore](type-aliases/SubscribePropsWithStore.md) +- [SubscribeSource](type-aliases/SubscribeSource.md) + +## Functions + +- [createTableHook](functions/createTableHook.md) +- [flexRender](functions/flexRender.md) +- [FlexRender](functions/FlexRender-1.md) +- [Subscribe](functions/Subscribe.md) +- [useTable](functions/useTable.md) diff --git a/docs/framework/react/reference/index/interfaces/AppCellComponent.md b/docs/framework/react/reference/index/interfaces/AppCellComponent.md new file mode 100644 index 0000000000..dba89e3e63 --- /dev/null +++ b/docs/framework/react/reference/index/interfaces/AppCellComponent.md @@ -0,0 +1,80 @@ +--- +id: AppCellComponent +title: AppCellComponent +--- + +# Interface: AppCellComponent()\ + +Defined in: [createTableHook.tsx:368](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L368) + +Component type for AppCell - wraps a cell and provides cell context with optional Subscribe + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +## Call Signature + +```ts +AppCellComponent(props): ReactNode; +``` + +Defined in: [createTableHook.tsx:373](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L373) + +Component type for AppCell - wraps a cell and provides cell context with optional Subscribe + +### Type Parameters + +#### TValue + +`TValue` *extends* `unknown` = `unknown` + +### Parameters + +#### props + +[`AppCellPropsWithoutSelector`](AppCellPropsWithoutSelector.md)\<`TFeatures`, `TData`, `TValue`, `TCellComponents`\> + +### Returns + +`ReactNode` + +## Call Signature + +```ts +AppCellComponent(props): ReactNode; +``` + +Defined in: [createTableHook.tsx:381](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L381) + +Component type for AppCell - wraps a cell and provides cell context with optional Subscribe + +### Type Parameters + +#### TValue + +`TValue` *extends* `unknown` = `unknown` + +#### TSelected + +`TSelected` = `unknown` + +### Parameters + +#### props + +[`AppCellPropsWithSelector`](AppCellPropsWithSelector.md)\<`TFeatures`, `TData`, `TValue`, `TCellComponents`, `TSelected`\> + +### Returns + +`ReactNode` diff --git a/docs/framework/react/reference/index/interfaces/AppCellPropsWithSelector.md b/docs/framework/react/reference/index/interfaces/AppCellPropsWithSelector.md new file mode 100644 index 0000000000..450dc39ea0 --- /dev/null +++ b/docs/framework/react/reference/index/interfaces/AppCellPropsWithSelector.md @@ -0,0 +1,86 @@ +--- +id: AppCellPropsWithSelector +title: AppCellPropsWithSelector +--- + +# Interface: AppCellPropsWithSelector\ + +Defined in: [createTableHook.tsx:313](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L313) + +Props for AppCell component - with selector + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +### TSelected + +`TSelected` + +## Properties + +### cell + +```ts +cell: Cell; +``` + +Defined in: [createTableHook.tsx:320](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L320) + +*** + +### children() + +```ts +children: (cell, state) => ReactNode; +``` + +Defined in: [createTableHook.tsx:321](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L321) + +#### Parameters + +##### cell + +`Cell_Cell`\<`TFeatures`, `TData`, `TValue`\> & `UnionToIntersection`\<`"columnGroupingFeature"` *extends* keyof `TFeatures` ? `Cell_ColumnGrouping` : `never`\> & `UnionToIntersection`\<\{ \[K in string \| number \| symbol\]: K extends "coreReativityFeature" ? never : TFeatures\[K\] extends TableFeature\ ? "Cell" extends keyof FeatureConstructorOptions ? FeatureConstructorOptions\[keyof (...) & "Cell"\] : never : any \}\[keyof `TFeatures`\]\> & `Cell_Plugins`\<`TFeatures`, `TData`, `TValue`\> & `TCellComponents` & `object` + +##### state + +`TSelected` + +#### Returns + +`ReactNode` + +*** + +### selector() + +```ts +selector: (state) => TSelected; +``` + +Defined in: [createTableHook.tsx:326](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L326) + +#### Parameters + +##### state + +`TableState`\<`TFeatures`\> + +#### Returns + +`TSelected` diff --git a/docs/framework/react/reference/index/interfaces/AppCellPropsWithoutSelector.md b/docs/framework/react/reference/index/interfaces/AppCellPropsWithoutSelector.md new file mode 100644 index 0000000000..be593f57dc --- /dev/null +++ b/docs/framework/react/reference/index/interfaces/AppCellPropsWithoutSelector.md @@ -0,0 +1,68 @@ +--- +id: AppCellPropsWithoutSelector +title: AppCellPropsWithoutSelector +--- + +# Interface: AppCellPropsWithoutSelector\ + +Defined in: [createTableHook.tsx:296](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L296) + +Props for AppCell component - without selector + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +## Properties + +### cell + +```ts +cell: Cell; +``` + +Defined in: [createTableHook.tsx:302](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L302) + +*** + +### children() + +```ts +children: (cell) => ReactNode; +``` + +Defined in: [createTableHook.tsx:303](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L303) + +#### Parameters + +##### cell + +`Cell_Cell`\<`TFeatures`, `TData`, `TValue`\> & `UnionToIntersection`\<`"columnGroupingFeature"` *extends* keyof `TFeatures` ? `Cell_ColumnGrouping` : `never`\> & `UnionToIntersection`\<\{ \[K in string \| number \| symbol\]: K extends "coreReativityFeature" ? never : TFeatures\[K\] extends TableFeature\ ? "Cell" extends keyof FeatureConstructorOptions ? FeatureConstructorOptions\[keyof (...) & "Cell"\] : never : any \}\[keyof `TFeatures`\]\> & `Cell_Plugins`\<`TFeatures`, `TData`, `TValue`\> & `TCellComponents` & `object` + +#### Returns + +`ReactNode` + +*** + +### selector? + +```ts +optional selector: undefined; +``` + +Defined in: [createTableHook.tsx:307](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L307) diff --git a/docs/framework/react/reference/index/interfaces/AppHeaderComponent.md b/docs/framework/react/reference/index/interfaces/AppHeaderComponent.md new file mode 100644 index 0000000000..c00749f75b --- /dev/null +++ b/docs/framework/react/reference/index/interfaces/AppHeaderComponent.md @@ -0,0 +1,80 @@ +--- +id: AppHeaderComponent +title: AppHeaderComponent +--- + +# Interface: AppHeaderComponent()\ + +Defined in: [createTableHook.tsx:395](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L395) + +Component type for AppHeader/AppFooter - wraps a header and provides header context with optional Subscribe + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +## Call Signature + +```ts +AppHeaderComponent(props): ReactNode; +``` + +Defined in: [createTableHook.tsx:400](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L400) + +Component type for AppHeader/AppFooter - wraps a header and provides header context with optional Subscribe + +### Type Parameters + +#### TValue + +`TValue` *extends* `unknown` = `unknown` + +### Parameters + +#### props + +[`AppHeaderPropsWithoutSelector`](AppHeaderPropsWithoutSelector.md)\<`TFeatures`, `TData`, `TValue`, `THeaderComponents`\> + +### Returns + +`ReactNode` + +## Call Signature + +```ts +AppHeaderComponent(props): ReactNode; +``` + +Defined in: [createTableHook.tsx:408](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L408) + +Component type for AppHeader/AppFooter - wraps a header and provides header context with optional Subscribe + +### Type Parameters + +#### TValue + +`TValue` *extends* `unknown` = `unknown` + +#### TSelected + +`TSelected` = `unknown` + +### Parameters + +#### props + +[`AppHeaderPropsWithSelector`](AppHeaderPropsWithSelector.md)\<`TFeatures`, `TData`, `TValue`, `THeaderComponents`, `TSelected`\> + +### Returns + +`ReactNode` diff --git a/docs/framework/react/reference/index/interfaces/AppHeaderPropsWithSelector.md b/docs/framework/react/reference/index/interfaces/AppHeaderPropsWithSelector.md new file mode 100644 index 0000000000..2aa3f06cc6 --- /dev/null +++ b/docs/framework/react/reference/index/interfaces/AppHeaderPropsWithSelector.md @@ -0,0 +1,88 @@ +--- +id: AppHeaderPropsWithSelector +title: AppHeaderPropsWithSelector +--- + +# Interface: AppHeaderPropsWithSelector\ + +Defined in: [createTableHook.tsx:349](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L349) + +Props for AppHeader/AppFooter component - with selector + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +### TSelected + +`TSelected` + +## Properties + +### children() + +```ts +children: (header, state) => ReactNode; +``` + +Defined in: [createTableHook.tsx:357](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L357) + +#### Parameters + +##### header + +`Header_Core`\<`TFeatures`, `TData`, `TValue`\> & `UnionToIntersection`\< + \| `"columnSizingFeature"` *extends* keyof `TFeatures` ? `Header_ColumnSizing` : `never` + \| `"columnResizingFeature"` *extends* keyof `TFeatures` ? `Header_ColumnResizing` : `never`\> & `UnionToIntersection`\<\{ \[K in string \| number \| symbol\]: K extends "coreReativityFeature" ? never : TFeatures\[K\] extends TableFeature\ ? "Header" extends keyof FeatureConstructorOptions ? FeatureConstructorOptions\[keyof (...) & "Header"\] : never : any \}\[keyof `TFeatures`\]\> & `Header_Plugins`\<`TFeatures`, `TData`, `TValue`\> & `THeaderComponents` & `object` + +##### state + +`TSelected` + +#### Returns + +`ReactNode` + +*** + +### header + +```ts +header: Header; +``` + +Defined in: [createTableHook.tsx:356](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L356) + +*** + +### selector() + +```ts +selector: (state) => TSelected; +``` + +Defined in: [createTableHook.tsx:362](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L362) + +#### Parameters + +##### state + +`TableState`\<`TFeatures`\> + +#### Returns + +`TSelected` diff --git a/docs/framework/react/reference/index/interfaces/AppHeaderPropsWithoutSelector.md b/docs/framework/react/reference/index/interfaces/AppHeaderPropsWithoutSelector.md new file mode 100644 index 0000000000..4636e9a882 --- /dev/null +++ b/docs/framework/react/reference/index/interfaces/AppHeaderPropsWithoutSelector.md @@ -0,0 +1,70 @@ +--- +id: AppHeaderPropsWithoutSelector +title: AppHeaderPropsWithoutSelector +--- + +# Interface: AppHeaderPropsWithoutSelector\ + +Defined in: [createTableHook.tsx:332](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L332) + +Props for AppHeader/AppFooter component - without selector + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +## Properties + +### children() + +```ts +children: (header) => ReactNode; +``` + +Defined in: [createTableHook.tsx:339](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L339) + +#### Parameters + +##### header + +`Header_Core`\<`TFeatures`, `TData`, `TValue`\> & `UnionToIntersection`\< + \| `"columnSizingFeature"` *extends* keyof `TFeatures` ? `Header_ColumnSizing` : `never` + \| `"columnResizingFeature"` *extends* keyof `TFeatures` ? `Header_ColumnResizing` : `never`\> & `UnionToIntersection`\<\{ \[K in string \| number \| symbol\]: K extends "coreReativityFeature" ? never : TFeatures\[K\] extends TableFeature\ ? "Header" extends keyof FeatureConstructorOptions ? FeatureConstructorOptions\[keyof (...) & "Header"\] : never : any \}\[keyof `TFeatures`\]\> & `Header_Plugins`\<`TFeatures`, `TData`, `TValue`\> & `THeaderComponents` & `object` + +#### Returns + +`ReactNode` + +*** + +### header + +```ts +header: Header; +``` + +Defined in: [createTableHook.tsx:338](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L338) + +*** + +### selector? + +```ts +optional selector: undefined; +``` + +Defined in: [createTableHook.tsx:343](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L343) diff --git a/docs/framework/react/reference/index/interfaces/AppTableComponent.md b/docs/framework/react/reference/index/interfaces/AppTableComponent.md new file mode 100644 index 0000000000..f23d4432ce --- /dev/null +++ b/docs/framework/react/reference/index/interfaces/AppTableComponent.md @@ -0,0 +1,62 @@ +--- +id: AppTableComponent +title: AppTableComponent +--- + +# Interface: AppTableComponent()\ + +Defined in: [createTableHook.tsx:422](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L422) + +Component type for AppTable - root wrapper with optional Subscribe + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +## Call Signature + +```ts +AppTableComponent(props): ReactNode; +``` + +Defined in: [createTableHook.tsx:423](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L423) + +Component type for AppTable - root wrapper with optional Subscribe + +### Parameters + +#### props + +[`AppTablePropsWithoutSelector`](AppTablePropsWithoutSelector.md) + +### Returns + +`ReactNode` + +## Call Signature + +```ts +AppTableComponent(props): ReactNode; +``` + +Defined in: [createTableHook.tsx:424](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L424) + +Component type for AppTable - root wrapper with optional Subscribe + +### Type Parameters + +#### TSelected + +`TSelected` + +### Parameters + +#### props + +[`AppTablePropsWithSelector`](AppTablePropsWithSelector.md)\<`TFeatures`, `TSelected`\> + +### Returns + +`ReactNode` diff --git a/docs/framework/react/reference/index/interfaces/AppTablePropsWithSelector.md b/docs/framework/react/reference/index/interfaces/AppTablePropsWithSelector.md new file mode 100644 index 0000000000..839cfd9710 --- /dev/null +++ b/docs/framework/react/reference/index/interfaces/AppTablePropsWithSelector.md @@ -0,0 +1,60 @@ +--- +id: AppTablePropsWithSelector +title: AppTablePropsWithSelector +--- + +# Interface: AppTablePropsWithSelector\ + +Defined in: [createTableHook.tsx:285](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L285) + +Props for AppTable component - with selector + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TSelected + +`TSelected` + +## Properties + +### children() + +```ts +children: (state) => ReactNode; +``` + +Defined in: [createTableHook.tsx:289](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L289) + +#### Parameters + +##### state + +`TSelected` + +#### Returns + +`ReactNode` + +*** + +### selector() + +```ts +selector: (state) => TSelected; +``` + +Defined in: [createTableHook.tsx:290](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L290) + +#### Parameters + +##### state + +`TableState`\<`TFeatures`\> + +#### Returns + +`TSelected` diff --git a/docs/framework/react/reference/index/interfaces/AppTablePropsWithoutSelector.md b/docs/framework/react/reference/index/interfaces/AppTablePropsWithoutSelector.md new file mode 100644 index 0000000000..76b4a54f99 --- /dev/null +++ b/docs/framework/react/reference/index/interfaces/AppTablePropsWithoutSelector.md @@ -0,0 +1,30 @@ +--- +id: AppTablePropsWithoutSelector +title: AppTablePropsWithoutSelector +--- + +# Interface: AppTablePropsWithoutSelector + +Defined in: [createTableHook.tsx:277](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L277) + +Props for AppTable component - without selector + +## Properties + +### children + +```ts +children: ReactNode; +``` + +Defined in: [createTableHook.tsx:278](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L278) + +*** + +### selector? + +```ts +optional selector: undefined; +``` + +Defined in: [createTableHook.tsx:279](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L279) diff --git a/docs/framework/react/reference/index/type-aliases/AppCellContext.md b/docs/framework/react/reference/index/type-aliases/AppCellContext.md new file mode 100644 index 0000000000..41efda8227 --- /dev/null +++ b/docs/framework/react/reference/index/type-aliases/AppCellContext.md @@ -0,0 +1,105 @@ +--- +id: AppCellContext +title: AppCellContext +--- + +# Type Alias: AppCellContext\ + +```ts +type AppCellContext = object; +``` + +Defined in: [createTableHook.tsx:41](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L41) + +Enhanced CellContext with pre-bound cell components. +The `cell` property includes the registered cellComponents. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +## Properties + +### cell + +```ts +cell: Cell & TCellComponents & object; +``` + +Defined in: [createTableHook.tsx:47](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L47) + +#### Type Declaration + +##### FlexRender() + +```ts +FlexRender: () => ReactNode; +``` + +###### Returns + +`ReactNode` + +*** + +### column + +```ts +column: Column; +``` + +Defined in: [createTableHook.tsx:49](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L49) + +*** + +### getValue + +```ts +getValue: CellContext["getValue"]; +``` + +Defined in: [createTableHook.tsx:50](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L50) + +*** + +### renderValue + +```ts +renderValue: CellContext["renderValue"]; +``` + +Defined in: [createTableHook.tsx:51](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L51) + +*** + +### row + +```ts +row: Row; +``` + +Defined in: [createTableHook.tsx:52](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L52) + +*** + +### table + +```ts +table: Table; +``` + +Defined in: [createTableHook.tsx:53](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L53) diff --git a/docs/framework/react/reference/index/type-aliases/AppColumnDefBase.md b/docs/framework/react/reference/index/type-aliases/AppColumnDefBase.md new file mode 100644 index 0000000000..ceb6b2efd3 --- /dev/null +++ b/docs/framework/react/reference/index/type-aliases/AppColumnDefBase.md @@ -0,0 +1,56 @@ +--- +id: AppColumnDefBase +title: AppColumnDefBase +--- + +# Type Alias: AppColumnDefBase\ + +```ts +type AppColumnDefBase = Omit, "cell" | "header" | "footer"> & object; +``` + +Defined in: [createTableHook.tsx:86](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L86) + +Enhanced column definition base with pre-bound components in cell/header/footer contexts. + +## Type Declaration + +### cell? + +```ts +optional cell: AppColumnDefTemplate>; +``` + +### footer? + +```ts +optional footer: AppColumnDefTemplate>; +``` + +### header? + +```ts +optional header: AppColumnDefTemplate>; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> diff --git a/docs/framework/react/reference/index/type-aliases/AppColumnDefTemplate.md b/docs/framework/react/reference/index/type-aliases/AppColumnDefTemplate.md new file mode 100644 index 0000000000..7faa582017 --- /dev/null +++ b/docs/framework/react/reference/index/type-aliases/AppColumnDefTemplate.md @@ -0,0 +1,20 @@ +--- +id: AppColumnDefTemplate +title: AppColumnDefTemplate +--- + +# Type Alias: AppColumnDefTemplate\ + +```ts +type AppColumnDefTemplate = string | (props) => any; +``` + +Defined in: [createTableHook.tsx:79](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L79) + +Template type for column definitions that can be a string or a function. + +## Type Parameters + +### TProps + +`TProps` *extends* `object` diff --git a/docs/framework/react/reference/index/type-aliases/AppColumnHelper.md b/docs/framework/react/reference/index/type-aliases/AppColumnHelper.md new file mode 100644 index 0000000000..f586cf5fc3 --- /dev/null +++ b/docs/framework/react/reference/index/type-aliases/AppColumnHelper.md @@ -0,0 +1,144 @@ +--- +id: AppColumnHelper +title: AppColumnHelper +--- + +# Type Alias: AppColumnHelper\ + +```ts +type AppColumnHelper = object; +``` + +Defined in: [createTableHook.tsx:162](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L162) + +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 Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +## Properties + +### accessor() + +```ts +accessor: (accessor, column) => TAccessor extends AccessorFn ? AccessorFnColumnDef : AccessorKeyColumnDef; +``` + +Defined in: [createTableHook.tsx:172](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L172) + +Creates a data column definition with an accessor key or function. +The cell, header, and footer contexts include pre-bound components. + +#### Type Parameters + +##### TAccessor + +`TAccessor` *extends* `AccessorFn`\<`TData`\> \| `DeepKeys`\<`TData`\> + +##### TValue + +`TValue` *extends* `TAccessor` *extends* `AccessorFn`\<`TData`, infer TReturn\> ? `TReturn` : `TAccessor` *extends* `DeepKeys`\<`TData`\> ? `DeepValue`\<`TData`, `TAccessor`\> : `never` + +#### Parameters + +##### accessor + +`TAccessor` + +##### column + +`TAccessor` *extends* `AccessorFn`\<`TData`\> ? [`AppColumnDefBase`](AppColumnDefBase.md)\<`TFeatures`, `TData`, `TValue`, `TCellComponents`, `THeaderComponents`\> & `object` : [`AppColumnDefBase`](AppColumnDefBase.md)\<`TFeatures`, `TData`, `TValue`, `TCellComponents`, `THeaderComponents`\> + +#### Returns + +`TAccessor` *extends* `AccessorFn`\<`TData`\> ? `AccessorFnColumnDef`\<`TFeatures`, `TData`, `TValue`\> : `AccessorKeyColumnDef`\<`TFeatures`, `TData`, `TValue`\> + +*** + +### columns() + +```ts +columns: (columns) => ColumnDef[] & [...TColumns]; +``` + +Defined in: [createTableHook.tsx:203](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L203) + +Wraps an array of column definitions to preserve each column's individual TValue type. + +#### Type Parameters + +##### TColumns + +`TColumns` *extends* `ReadonlyArray`\<`ColumnDef`\<`TFeatures`, `TData`, `any`\>\> + +#### Parameters + +##### columns + +\[`...TColumns`\] + +#### Returns + +`ColumnDef`\<`TFeatures`, `TData`, `any`\>[] & \[`...TColumns`\] + +*** + +### display() + +```ts +display: (column) => DisplayColumnDef; +``` + +Defined in: [createTableHook.tsx:211](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L211) + +Creates a display column definition for non-data columns. +The cell, header, and footer contexts include pre-bound components. + +#### Parameters + +##### column + +[`AppDisplayColumnDef`](AppDisplayColumnDef.md)\<`TFeatures`, `TData`, `TCellComponents`, `THeaderComponents`\> + +#### Returns + +`DisplayColumnDef`\<`TFeatures`, `TData`, `unknown`\> + +*** + +### group() + +```ts +group: (column) => GroupColumnDef; +``` + +Defined in: [createTableHook.tsx:224](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L224) + +Creates a group column definition with nested child columns. +The cell, header, and footer contexts include pre-bound components. + +#### Parameters + +##### column + +[`AppGroupColumnDef`](AppGroupColumnDef.md)\<`TFeatures`, `TData`, `TCellComponents`, `THeaderComponents`\> + +#### Returns + +`GroupColumnDef`\<`TFeatures`, `TData`, `unknown`\> diff --git a/docs/framework/react/reference/index/type-aliases/AppDisplayColumnDef.md b/docs/framework/react/reference/index/type-aliases/AppDisplayColumnDef.md new file mode 100644 index 0000000000..cf769c9d54 --- /dev/null +++ b/docs/framework/react/reference/index/type-aliases/AppDisplayColumnDef.md @@ -0,0 +1,52 @@ +--- +id: AppDisplayColumnDef +title: AppDisplayColumnDef +--- + +# Type Alias: AppDisplayColumnDef\ + +```ts +type AppDisplayColumnDef = Omit, "cell" | "header" | "footer"> & object; +``` + +Defined in: [createTableHook.tsx:110](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L110) + +Enhanced display column definition with pre-bound components. + +## Type Declaration + +### cell? + +```ts +optional cell: AppColumnDefTemplate>; +``` + +### footer? + +```ts +optional footer: AppColumnDefTemplate>; +``` + +### header? + +```ts +optional header: AppColumnDefTemplate>; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> diff --git a/docs/framework/react/reference/index/type-aliases/AppGroupColumnDef.md b/docs/framework/react/reference/index/type-aliases/AppGroupColumnDef.md new file mode 100644 index 0000000000..00409ac236 --- /dev/null +++ b/docs/framework/react/reference/index/type-aliases/AppGroupColumnDef.md @@ -0,0 +1,58 @@ +--- +id: AppGroupColumnDef +title: AppGroupColumnDef +--- + +# Type Alias: AppGroupColumnDef\ + +```ts +type AppGroupColumnDef = Omit, "cell" | "header" | "footer" | "columns"> & object; +``` + +Defined in: [createTableHook.tsx:133](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L133) + +Enhanced group column definition with pre-bound components. + +## Type Declaration + +### cell? + +```ts +optional cell: AppColumnDefTemplate>; +``` + +### columns? + +```ts +optional columns: ColumnDef[]; +``` + +### footer? + +```ts +optional footer: AppColumnDefTemplate>; +``` + +### header? + +```ts +optional header: AppColumnDefTemplate>; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> diff --git a/docs/framework/react/reference/index/type-aliases/AppHeaderContext.md b/docs/framework/react/reference/index/type-aliases/AppHeaderContext.md new file mode 100644 index 0000000000..976c52cbeb --- /dev/null +++ b/docs/framework/react/reference/index/type-aliases/AppHeaderContext.md @@ -0,0 +1,75 @@ +--- +id: AppHeaderContext +title: AppHeaderContext +--- + +# Type Alias: AppHeaderContext\ + +```ts +type AppHeaderContext = object; +``` + +Defined in: [createTableHook.tsx:60](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L60) + +Enhanced HeaderContext with pre-bound header components. +The `header` property includes the registered headerComponents. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +## Properties + +### column + +```ts +column: Column; +``` + +Defined in: [createTableHook.tsx:66](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L66) + +*** + +### header + +```ts +header: Header & THeaderComponents & object; +``` + +Defined in: [createTableHook.tsx:67](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L67) + +#### Type Declaration + +##### FlexRender() + +```ts +FlexRender: () => ReactNode; +``` + +###### Returns + +`ReactNode` + +*** + +### table + +```ts +table: Table; +``` + +Defined in: [createTableHook.tsx:69](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L69) diff --git a/docs/framework/react/reference/index/type-aliases/AppReactTable.md b/docs/framework/react/reference/index/type-aliases/AppReactTable.md new file mode 100644 index 0000000000..670aff204b --- /dev/null +++ b/docs/framework/react/reference/index/type-aliases/AppReactTable.md @@ -0,0 +1,127 @@ +--- +id: AppReactTable +title: AppReactTable +--- + +# Type Alias: AppReactTable\ + +```ts +type AppReactTable = ReactTable & NoInfer & object; +``` + +Defined in: [createTableHook.tsx:430](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L430) + +Extended table API returned by useAppTable with all App wrapper components + +## Type Declaration + +### AppCell + +```ts +AppCell: AppCellComponent>; +``` + +Wraps a cell and provides cell context with pre-bound cellComponents. +Optionally accepts a selector for Subscribe functionality. + +#### Example + +```tsx +// Without selector + + {(c) => } + + +// With selector - children receives cell and selected state + s.columnFilters}> + {(c, filters) => {filters.length}} + +``` + +### AppFooter + +```ts +AppFooter: AppHeaderComponent>; +``` + +Wraps a footer and provides header context with pre-bound headerComponents. +Optionally accepts a selector for Subscribe functionality. + +#### Example + +```tsx + + {(f) => } + +``` + +### AppHeader + +```ts +AppHeader: AppHeaderComponent>; +``` + +Wraps a header and provides header context with pre-bound headerComponents. +Optionally accepts a selector for Subscribe functionality. + +#### Example + +```tsx +// Without selector + + {(h) => } + + +// With selector + s.sorting}> + {(h, sorting) => {sorting.length} sorted} + +``` + +### AppTable + +```ts +AppTable: AppTableComponent; +``` + +Root wrapper component that provides table context with optional Subscribe. + +#### Example + +```tsx +// Without selector - children is ReactNode + + ...
+
+ +// With selector - children receives selected state + s.pagination}> + {(pagination) =>
Page {pagination.pageIndex}
} +
+``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TSelected + +`TSelected` + +### TTableComponents + +`TTableComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> diff --git a/docs/framework/react/reference/index/type-aliases/CreateTableHookOptions.md b/docs/framework/react/reference/index/type-aliases/CreateTableHookOptions.md new file mode 100644 index 0000000000..a1ddac5529 --- /dev/null +++ b/docs/framework/react/reference/index/type-aliases/CreateTableHookOptions.md @@ -0,0 +1,83 @@ +--- +id: CreateTableHookOptions +title: CreateTableHookOptions +--- + +# Type Alias: CreateTableHookOptions\ + +```ts +type CreateTableHookOptions = Omit, "columns" | "data" | "store" | "state" | "initialState"> & object; +``` + +Defined in: [createTableHook.tsx:242](https://github.com/TanStack/table/blob/main/packages/react-table/src/createTableHook.tsx#L242) + +Options for creating a table hook with pre-bound components and default table options. +Extends all TableOptions except 'columns' | 'data' | 'store' | 'state' | 'initialState'. + +## Type Declaration + +### cellComponents? + +```ts +optional cellComponents: TCellComponents; +``` + +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 + +```ts +{ TextCell, NumberCell, DateCell, CurrencyCell } +``` + +### headerComponents? + +```ts +optional headerComponents: THeaderComponents; +``` + +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 + +```ts +{ SortIndicator, ColumnFilter, ResizeHandle } +``` + +### tableComponents? + +```ts +optional tableComponents: TTableComponents; +``` + +Table-level components that need access to the table instance. +These are available directly on the table object returned by useAppTable. +Use `useTableContext()` inside these components. + +#### Example + +```ts +{ PaginationControls, GlobalFilter, RowCount } +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TTableComponents + +`TTableComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, `ComponentType`\<`any`\>\> diff --git a/docs/framework/react/reference/index/type-aliases/FlexRenderProps.md b/docs/framework/react/reference/index/type-aliases/FlexRenderProps.md new file mode 100644 index 0000000000..f4bdb1ca51 --- /dev/null +++ b/docs/framework/react/reference/index/type-aliases/FlexRenderProps.md @@ -0,0 +1,58 @@ +--- +id: FlexRenderProps +title: FlexRenderProps +--- + +# Type Alias: FlexRenderProps\ + +```ts +type FlexRenderProps = + | { + cell: Cell; + footer?: never; + header?: never; +} + | { + cell?: never; + footer?: never; + header: Header; +} + | { + cell?: never; + footer: Header; + header?: never; +}; +``` + +Defined in: [FlexRender.tsx:63](https://github.com/TanStack/table/blob/main/packages/react-table/src/FlexRender.tsx#L63) + +Simplified component wrapper of `flexRender`. Use this utility component to render headers, cells, or footers with custom markup. +Only one prop (`cell`, `header`, or `footer`) may be passed. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` = `CellData` + +## Examples + +```ts + +``` + +```ts + +``` + +```ts + +``` diff --git a/docs/framework/react/reference/index/type-aliases/ReactTable.md b/docs/framework/react/reference/index/type-aliases/ReactTable.md new file mode 100644 index 0000000000..ecf261b102 --- /dev/null +++ b/docs/framework/react/reference/index/type-aliases/ReactTable.md @@ -0,0 +1,193 @@ +--- +id: ReactTable +title: ReactTable +--- + +# Type Alias: ReactTable\ + +```ts +type ReactTable = Table & object; +``` + +Defined in: [useTable.ts:21](https://github.com/TanStack/table/blob/main/packages/react-table/src/useTable.ts#L21) + +## Type Declaration + +### FlexRender() + +```ts +FlexRender: (props) => ReactNode; +``` + +A React component that renders headers, cells, or footers with custom markup. +Use this utility component instead of manually calling flexRender. + +#### Type Parameters + +##### TValue + +`TValue` *extends* `CellData` = `CellData` + +#### Parameters + +##### props + +[`FlexRenderProps`](FlexRenderProps.md)\<`TFeatures`, `TData`, `TValue`\> + +#### Returns + +`ReactNode` + +#### Example + +```tsx + + + +``` + +This replaces calling `flexRender` directly like this: +```tsx +flexRender(cell.column.columnDef.cell, cell.getContext()) +flexRender(header.column.columnDef.header, header.getContext()) +flexRender(footer.column.columnDef.footer, footer.getContext()) +``` + +### state + +```ts +readonly state: Readonly; +``` + +The selected state of the table. This state may not match the structure of `table.store.state` because it is selected by the `selector` function that you pass as the 2nd argument to `useTable`. + +#### Example + +```ts +const table = useTable(options, (state) => ({ globalFilter: state.globalFilter })) // only globalFilter is part of the selected state + +console.log(table.state.globalFilter) +``` + +### Subscribe() + +```ts +Subscribe: { + (props): ReactNode | Promise; + (props): ReactNode | Promise; + (props): ReactNode | Promise; +}; +``` + +Overloads (not a single union) so `selector` callbacks get correct contextual +types in JSX; a union of two `selector` signatures degrades to implicit `any`. + +Source **without** `selector` is a separate overload so children receive `TSourceValue` +(identity projection). If `selector` were optional on one overload, `TSubSelected` +would default to `unknown` instead of inferring from the source. + +The **source** overloads are listed first so `TSourceValue` is inferred from `source`. + +#### Call Signature + +```ts +(props): ReactNode | Promise; +``` + +##### Type Parameters + +###### TSourceValue + +`TSourceValue` + +##### Parameters + +###### props + +###### children + +(`state`) => `ReactNode` \| `ReactNode` + +###### selector? + +`undefined` + +###### source + +[`SubscribeSource`](SubscribeSource.md)\<`TSourceValue`\> + +##### Returns + +`ReactNode` \| `Promise`\<`ReactNode`\> + +#### Call Signature + +```ts +(props): ReactNode | Promise; +``` + +##### Type Parameters + +###### TSourceValue + +`TSourceValue` + +###### TSubSelected + +`TSubSelected` + +##### Parameters + +###### props + +###### children + +(`state`) => `ReactNode` \| `ReactNode` + +###### selector + +(`state`) => `TSubSelected` + +###### source + +[`SubscribeSource`](SubscribeSource.md)\<`TSourceValue`\> + +##### Returns + +`ReactNode` \| `Promise`\<`ReactNode`\> + +#### Call Signature + +```ts +(props): ReactNode | Promise; +``` + +##### Type Parameters + +###### TSubSelected + +`TSubSelected` + +##### Parameters + +###### props + +`Omit`\<[`SubscribePropsWithStore`](SubscribePropsWithStore.md)\<`TFeatures`, `TSubSelected`\>, `"source"`\> + +##### Returns + +`ReactNode` \| `Promise`\<`ReactNode`\> + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TSelected + +`TSelected` = `TableState`\<`TFeatures`\> diff --git a/docs/framework/react/reference/index/type-aliases/Renderable.md b/docs/framework/react/reference/index/type-aliases/Renderable.md new file mode 100644 index 0000000000..745a7f1b66 --- /dev/null +++ b/docs/framework/react/reference/index/type-aliases/Renderable.md @@ -0,0 +1,18 @@ +--- +id: Renderable +title: Renderable +--- + +# Type Alias: Renderable\ + +```ts +type Renderable = ReactNode | ComponentType; +``` + +Defined in: [FlexRender.tsx:11](https://github.com/TanStack/table/blob/main/packages/react-table/src/FlexRender.tsx#L11) + +## Type Parameters + +### TProps + +`TProps` diff --git a/docs/framework/react/reference/index/type-aliases/SubscribeProps.md b/docs/framework/react/reference/index/type-aliases/SubscribeProps.md new file mode 100644 index 0000000000..41ebae0626 --- /dev/null +++ b/docs/framework/react/reference/index/type-aliases/SubscribeProps.md @@ -0,0 +1,29 @@ +--- +id: SubscribeProps +title: SubscribeProps +--- + +# Type Alias: SubscribeProps\ + +```ts +type SubscribeProps = + | SubscribePropsWithStore + | SubscribePropsWithSourceIdentity +| SubscribePropsWithSourceWithSelector; +``` + +Defined in: [Subscribe.ts:69](https://github.com/TanStack/table/blob/main/packages/react-table/src/Subscribe.ts#L69) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TSelected + +`TSelected` = `unknown` + +### TSourceValue + +`TSourceValue` = `unknown` diff --git a/docs/framework/react/reference/index/type-aliases/SubscribePropsWithSource.md b/docs/framework/react/reference/index/type-aliases/SubscribePropsWithSource.md new file mode 100644 index 0000000000..e08ee8d1d1 --- /dev/null +++ b/docs/framework/react/reference/index/type-aliases/SubscribePropsWithSource.md @@ -0,0 +1,28 @@ +--- +id: SubscribePropsWithSource +title: SubscribePropsWithSource +--- + +# Type Alias: SubscribePropsWithSource\ + +```ts +type SubscribePropsWithSource = + | SubscribePropsWithSourceIdentity +| SubscribePropsWithSourceWithSelector; +``` + +Defined in: [Subscribe.ts:65](https://github.com/TanStack/table/blob/main/packages/react-table/src/Subscribe.ts#L65) + +Subscribe to a single source — atom or store (identity or projected). Prefer +[SubscribePropsWithSourceIdentity](SubscribePropsWithSourceIdentity.md) or [SubscribePropsWithSourceWithSelector](SubscribePropsWithSourceWithSelector.md) +for clearer inference when `selector` is omitted. + +## Type Parameters + +### TSourceValue + +`TSourceValue` + +### TSelected + +`TSelected` = `TSourceValue` diff --git a/docs/framework/react/reference/index/type-aliases/SubscribePropsWithSourceIdentity.md b/docs/framework/react/reference/index/type-aliases/SubscribePropsWithSourceIdentity.md new file mode 100644 index 0000000000..311890ced1 --- /dev/null +++ b/docs/framework/react/reference/index/type-aliases/SubscribePropsWithSourceIdentity.md @@ -0,0 +1,52 @@ +--- +id: SubscribePropsWithSourceIdentity +title: SubscribePropsWithSourceIdentity +--- + +# Type Alias: SubscribePropsWithSourceIdentity\ + +```ts +type SubscribePropsWithSourceIdentity = object; +``` + +Defined in: [Subscribe.ts:44](https://github.com/TanStack/table/blob/main/packages/react-table/src/Subscribe.ts#L44) + +Subscribe to the full value of a source (e.g. `table.atoms.rowSelection` or +`table.optionsStore`). Omitting `selector` is equivalent to the identity +selector — children receive `TSourceValue`. + +## Type Parameters + +### TSourceValue + +`TSourceValue` + +## Properties + +### children + +```ts +children: (state) => ReactNode | ReactNode; +``` + +Defined in: [Subscribe.ts:47](https://github.com/TanStack/table/blob/main/packages/react-table/src/Subscribe.ts#L47) + +*** + +### selector? + +```ts +optional selector: undefined; +``` + +Defined in: [Subscribe.ts:46](https://github.com/TanStack/table/blob/main/packages/react-table/src/Subscribe.ts#L46) + +*** + +### source + +```ts +source: SubscribeSource; +``` + +Defined in: [Subscribe.ts:45](https://github.com/TanStack/table/blob/main/packages/react-table/src/Subscribe.ts#L45) diff --git a/docs/framework/react/reference/index/type-aliases/SubscribePropsWithSourceWithSelector.md b/docs/framework/react/reference/index/type-aliases/SubscribePropsWithSourceWithSelector.md new file mode 100644 index 0000000000..5992d99ea1 --- /dev/null +++ b/docs/framework/react/reference/index/type-aliases/SubscribePropsWithSourceWithSelector.md @@ -0,0 +1,65 @@ +--- +id: SubscribePropsWithSourceWithSelector +title: SubscribePropsWithSourceWithSelector +--- + +# Type Alias: SubscribePropsWithSourceWithSelector\ + +```ts +type SubscribePropsWithSourceWithSelector = object; +``` + +Defined in: [Subscribe.ts:54](https://github.com/TanStack/table/blob/main/packages/react-table/src/Subscribe.ts#L54) + +Subscribe to a projected value from a source (atom or store). The selector +receives the source value; children receive the projected `TSelected`. + +## Type Parameters + +### TSourceValue + +`TSourceValue` + +### TSelected + +`TSelected` + +## Properties + +### children + +```ts +children: (state) => ReactNode | ReactNode; +``` + +Defined in: [Subscribe.ts:57](https://github.com/TanStack/table/blob/main/packages/react-table/src/Subscribe.ts#L57) + +*** + +### selector() + +```ts +selector: (state) => TSelected; +``` + +Defined in: [Subscribe.ts:56](https://github.com/TanStack/table/blob/main/packages/react-table/src/Subscribe.ts#L56) + +#### Parameters + +##### state + +`TSourceValue` + +#### Returns + +`TSelected` + +*** + +### source + +```ts +source: SubscribeSource; +``` + +Defined in: [Subscribe.ts:55](https://github.com/TanStack/table/blob/main/packages/react-table/src/Subscribe.ts#L55) diff --git a/docs/framework/react/reference/index/type-aliases/SubscribePropsWithStore.md b/docs/framework/react/reference/index/type-aliases/SubscribePropsWithStore.md new file mode 100644 index 0000000000..56a55531c6 --- /dev/null +++ b/docs/framework/react/reference/index/type-aliases/SubscribePropsWithStore.md @@ -0,0 +1,71 @@ +--- +id: SubscribePropsWithStore +title: SubscribePropsWithStore +--- + +# Type Alias: SubscribePropsWithStore\ + +```ts +type SubscribePropsWithStore = object; +``` + +Defined in: [Subscribe.ts:23](https://github.com/TanStack/table/blob/main/packages/react-table/src/Subscribe.ts#L23) + +Subscribe to `table.store` (full table state). The selector receives the full +TableState. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TSelected + +`TSelected` + +## Properties + +### children + +```ts +children: (state) => ReactNode | ReactNode; +``` + +Defined in: [Subscribe.ts:36](https://github.com/TanStack/table/blob/main/packages/react-table/src/Subscribe.ts#L36) + +*** + +### selector() + +```ts +selector: (state) => TSelected; +``` + +Defined in: [Subscribe.ts:35](https://github.com/TanStack/table/blob/main/packages/react-table/src/Subscribe.ts#L35) + +Select from full table state. Re-renders when the selected value changes +(shallow compare). + +Required in store mode so you never accidentally subscribe to the whole +store without an explicit projection. + +#### Parameters + +##### state + +`TableState`\<`TFeatures`\> + +#### Returns + +`TSelected` + +*** + +### source + +```ts +source: SubscribeSource>; +``` + +Defined in: [Subscribe.ts:27](https://github.com/TanStack/table/blob/main/packages/react-table/src/Subscribe.ts#L27) diff --git a/docs/framework/react/reference/index/type-aliases/SubscribeSource.md b/docs/framework/react/reference/index/type-aliases/SubscribeSource.md new file mode 100644 index 0000000000..ed0adc6acc --- /dev/null +++ b/docs/framework/react/reference/index/type-aliases/SubscribeSource.md @@ -0,0 +1,22 @@ +--- +id: SubscribeSource +title: SubscribeSource +--- + +# Type Alias: SubscribeSource\ + +```ts +type SubscribeSource = + | Atom + | ReadonlyAtom + | Store +| ReadonlyStore; +``` + +Defined in: [Subscribe.ts:13](https://github.com/TanStack/table/blob/main/packages/react-table/src/Subscribe.ts#L13) + +## Type Parameters + +### TValue + +`TValue` diff --git a/docs/framework/react/reference/legacy/functions/getCoreRowModel.md b/docs/framework/react/reference/legacy/functions/getCoreRowModel.md new file mode 100644 index 0000000000..9af0642090 --- /dev/null +++ b/docs/framework/react/reference/legacy/functions/getCoreRowModel.md @@ -0,0 +1,29 @@ +--- +id: getCoreRowModel +title: getCoreRowModel +--- + +# ~~Function: getCoreRowModel()~~ + +```ts +function getCoreRowModel(): RowModelFactory; +``` + +Defined in: [useLegacyTable.ts:149](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L149) + +## Type Parameters + +### TData + +`TData` *extends* `RowData` + +## Returns + +[`RowModelFactory`](../type-aliases/RowModelFactory.md)\<`TData`\> + +## Deprecated + +The core row model is always created automatically in v9. + +This is a stub function for v8 API compatibility with `useLegacyTable`. +It does nothing - the core row model is always available. diff --git a/docs/framework/react/reference/legacy/functions/getExpandedRowModel.md b/docs/framework/react/reference/legacy/functions/getExpandedRowModel.md new file mode 100644 index 0000000000..7748277456 --- /dev/null +++ b/docs/framework/react/reference/legacy/functions/getExpandedRowModel.md @@ -0,0 +1,29 @@ +--- +id: getExpandedRowModel +title: getExpandedRowModel +--- + +# ~~Function: getExpandedRowModel()~~ + +```ts +function getExpandedRowModel(): RowModelFactory; +``` + +Defined in: [useLegacyTable.ts:89](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L89) + +## Type Parameters + +### TData + +`TData` *extends* `RowData` + +## Returns + +[`RowModelFactory`](../type-aliases/RowModelFactory.md)\<`TData`\> + +## Deprecated + +Use `createExpandedRowModel()` with the new `useTable` hook instead. + +This is a stub function for v8 API compatibility with `useLegacyTable`. +It acts as a marker to enable the expanded row model. diff --git a/docs/framework/react/reference/legacy/functions/getFacetedMinMaxValues.md b/docs/framework/react/reference/legacy/functions/getFacetedMinMaxValues.md new file mode 100644 index 0000000000..04f865f813 --- /dev/null +++ b/docs/framework/react/reference/legacy/functions/getFacetedMinMaxValues.md @@ -0,0 +1,29 @@ +--- +id: getFacetedMinMaxValues +title: getFacetedMinMaxValues +--- + +# ~~Function: getFacetedMinMaxValues()~~ + +```ts +function getFacetedMinMaxValues(): FacetedMinMaxValuesFactory; +``` + +Defined in: [useLegacyTable.ts:125](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L125) + +## Type Parameters + +### TData + +`TData` *extends* `RowData` + +## Returns + +[`FacetedMinMaxValuesFactory`](../type-aliases/FacetedMinMaxValuesFactory.md)\<`TData`\> + +## Deprecated + +Use `createFacetedMinMaxValues()` with the new `useTable` hook instead. + +This is a stub function for v8 API compatibility with `useLegacyTable`. +It acts as a marker to enable the faceted min/max values. diff --git a/docs/framework/react/reference/legacy/functions/getFacetedRowModel.md b/docs/framework/react/reference/legacy/functions/getFacetedRowModel.md new file mode 100644 index 0000000000..eaf2e14d0c --- /dev/null +++ b/docs/framework/react/reference/legacy/functions/getFacetedRowModel.md @@ -0,0 +1,29 @@ +--- +id: getFacetedRowModel +title: getFacetedRowModel +--- + +# ~~Function: getFacetedRowModel()~~ + +```ts +function getFacetedRowModel(): FacetedRowModelFactory; +``` + +Defined in: [useLegacyTable.ts:113](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L113) + +## Type Parameters + +### TData + +`TData` *extends* `RowData` + +## Returns + +[`FacetedRowModelFactory`](../type-aliases/FacetedRowModelFactory.md)\<`TData`\> + +## Deprecated + +Use `createFacetedRowModel()` with the new `useTable` hook instead. + +This is a stub function for v8 API compatibility with `useLegacyTable`. +It acts as a marker to enable the faceted row model. diff --git a/docs/framework/react/reference/legacy/functions/getFacetedUniqueValues.md b/docs/framework/react/reference/legacy/functions/getFacetedUniqueValues.md new file mode 100644 index 0000000000..bfad0c8d02 --- /dev/null +++ b/docs/framework/react/reference/legacy/functions/getFacetedUniqueValues.md @@ -0,0 +1,29 @@ +--- +id: getFacetedUniqueValues +title: getFacetedUniqueValues +--- + +# ~~Function: getFacetedUniqueValues()~~ + +```ts +function getFacetedUniqueValues(): FacetedUniqueValuesFactory; +``` + +Defined in: [useLegacyTable.ts:137](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L137) + +## Type Parameters + +### TData + +`TData` *extends* `RowData` + +## Returns + +[`FacetedUniqueValuesFactory`](../type-aliases/FacetedUniqueValuesFactory.md)\<`TData`\> + +## Deprecated + +Use `createFacetedUniqueValues()` with the new `useTable` hook instead. + +This is a stub function for v8 API compatibility with `useLegacyTable`. +It acts as a marker to enable the faceted unique values. diff --git a/docs/framework/react/reference/legacy/functions/getFilteredRowModel.md b/docs/framework/react/reference/legacy/functions/getFilteredRowModel.md new file mode 100644 index 0000000000..3ecb26e39f --- /dev/null +++ b/docs/framework/react/reference/legacy/functions/getFilteredRowModel.md @@ -0,0 +1,29 @@ +--- +id: getFilteredRowModel +title: getFilteredRowModel +--- + +# ~~Function: getFilteredRowModel()~~ + +```ts +function getFilteredRowModel(): RowModelFactory; +``` + +Defined in: [useLegacyTable.ts:53](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L53) + +## Type Parameters + +### TData + +`TData` *extends* `RowData` + +## Returns + +[`RowModelFactory`](../type-aliases/RowModelFactory.md)\<`TData`\> + +## Deprecated + +Use `createFilteredRowModel(filterFns)` with the new `useTable` hook instead. + +This is a stub function for v8 API compatibility with `useLegacyTable`. +It acts as a marker to enable the filtered row model. diff --git a/docs/framework/react/reference/legacy/functions/getGroupedRowModel.md b/docs/framework/react/reference/legacy/functions/getGroupedRowModel.md new file mode 100644 index 0000000000..92b173ea8d --- /dev/null +++ b/docs/framework/react/reference/legacy/functions/getGroupedRowModel.md @@ -0,0 +1,29 @@ +--- +id: getGroupedRowModel +title: getGroupedRowModel +--- + +# ~~Function: getGroupedRowModel()~~ + +```ts +function getGroupedRowModel(): RowModelFactory; +``` + +Defined in: [useLegacyTable.ts:101](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L101) + +## Type Parameters + +### TData + +`TData` *extends* `RowData` + +## Returns + +[`RowModelFactory`](../type-aliases/RowModelFactory.md)\<`TData`\> + +## Deprecated + +Use `createGroupedRowModel(aggregationFns)` with the new `useTable` hook instead. + +This is a stub function for v8 API compatibility with `useLegacyTable`. +It acts as a marker to enable the grouped row model. diff --git a/docs/framework/react/reference/legacy/functions/getPaginationRowModel.md b/docs/framework/react/reference/legacy/functions/getPaginationRowModel.md new file mode 100644 index 0000000000..e0f8cf72d7 --- /dev/null +++ b/docs/framework/react/reference/legacy/functions/getPaginationRowModel.md @@ -0,0 +1,29 @@ +--- +id: getPaginationRowModel +title: getPaginationRowModel +--- + +# ~~Function: getPaginationRowModel()~~ + +```ts +function getPaginationRowModel(): RowModelFactory; +``` + +Defined in: [useLegacyTable.ts:77](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L77) + +## Type Parameters + +### TData + +`TData` *extends* `RowData` + +## Returns + +[`RowModelFactory`](../type-aliases/RowModelFactory.md)\<`TData`\> + +## Deprecated + +Use `createPaginatedRowModel()` with the new `useTable` hook instead. + +This is a stub function for v8 API compatibility with `useLegacyTable`. +It acts as a marker to enable the paginated row model. diff --git a/docs/framework/react/reference/legacy/functions/getSortedRowModel.md b/docs/framework/react/reference/legacy/functions/getSortedRowModel.md new file mode 100644 index 0000000000..53ed570ccf --- /dev/null +++ b/docs/framework/react/reference/legacy/functions/getSortedRowModel.md @@ -0,0 +1,29 @@ +--- +id: getSortedRowModel +title: getSortedRowModel +--- + +# ~~Function: getSortedRowModel()~~ + +```ts +function getSortedRowModel(): RowModelFactory; +``` + +Defined in: [useLegacyTable.ts:65](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L65) + +## Type Parameters + +### TData + +`TData` *extends* `RowData` + +## Returns + +[`RowModelFactory`](../type-aliases/RowModelFactory.md)\<`TData`\> + +## Deprecated + +Use `createSortedRowModel(sortFns)` with the new `useTable` hook instead. + +This is a stub function for v8 API compatibility with `useLegacyTable`. +It acts as a marker to enable the sorted row model. diff --git a/docs/framework/react/reference/legacy/functions/legacyCreateColumnHelper.md b/docs/framework/react/reference/legacy/functions/legacyCreateColumnHelper.md new file mode 100644 index 0000000000..543816bf5c --- /dev/null +++ b/docs/framework/react/reference/legacy/functions/legacyCreateColumnHelper.md @@ -0,0 +1,29 @@ +--- +id: legacyCreateColumnHelper +title: legacyCreateColumnHelper +--- + +# ~~Function: legacyCreateColumnHelper()~~ + +```ts +function legacyCreateColumnHelper(): ColumnHelper; +``` + +Defined in: [useLegacyTable.ts:345](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L345) + +## Type Parameters + +### TData + +`TData` *extends* `RowData` + +## Returns + +`ColumnHelper`\<`StockFeatures`, `TData`\> + +## Deprecated + +Use `createColumnHelper()` with useTable instead. + +A column helper with StockFeatures pre-bound for use with useLegacyTable. +Only requires TData—no need to specify TFeatures. diff --git a/docs/framework/react/reference/legacy/functions/useLegacyTable.md b/docs/framework/react/reference/legacy/functions/useLegacyTable.md new file mode 100644 index 0000000000..44b191fe9f --- /dev/null +++ b/docs/framework/react/reference/legacy/functions/useLegacyTable.md @@ -0,0 +1,64 @@ +--- +id: useLegacyTable +title: useLegacyTable +--- + +# ~~Function: useLegacyTable()~~ + +```ts +function useLegacyTable(options): LegacyReactTable; +``` + +Defined in: [useLegacyTable.ts:387](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L387) + +## Type Parameters + +### TData + +`TData` *extends* `RowData` + +## Parameters + +### options + +[`LegacyTableOptions`](../type-aliases/LegacyTableOptions.md)\<`TData`\> + +Legacy v8-style table options + +## Returns + +[`LegacyReactTable`](../type-aliases/LegacyReactTable.md)\<`TData`\> + +A table instance with the full state subscribed and a `getState()` method + +## Deprecated + +This hook is provided as a compatibility layer for migrating from TanStack Table v8. + +Use the new `useTable` hook instead with explicit `_features` and `_rowModels`: + +```tsx +// New v9 API +const _features = tableFeatures({ + columnFilteringFeature, + rowSortingFeature, + rowPaginationFeature, +}) + +const table = useTable({ + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data, +}) +``` + +Key differences from v8: +- Features are tree-shakeable - only import what you use +- Row models are explicitly passed via `_rowModels` +- Use `table.Subscribe` for fine-grained re-renders +- State is accessed via `table.state` after selecting with the 2nd argument diff --git a/docs/framework/react/reference/legacy/index.md b/docs/framework/react/reference/legacy/index.md new file mode 100644 index 0000000000..821a696215 --- /dev/null +++ b/docs/framework/react/reference/legacy/index.md @@ -0,0 +1,40 @@ +--- +id: legacy +title: legacy +--- + +# legacy + +## Interfaces + +- [LegacyRowModelOptions](interfaces/LegacyRowModelOptions.md) + +## Type Aliases + +- [FacetedMinMaxValuesFactory](type-aliases/FacetedMinMaxValuesFactory.md) +- [FacetedRowModelFactory](type-aliases/FacetedRowModelFactory.md) +- [FacetedUniqueValuesFactory](type-aliases/FacetedUniqueValuesFactory.md) +- [~~LegacyCell~~](type-aliases/LegacyCell.md) +- [~~LegacyColumn~~](type-aliases/LegacyColumn.md) +- [~~LegacyColumnDef~~](type-aliases/LegacyColumnDef.md) +- [~~LegacyHeader~~](type-aliases/LegacyHeader.md) +- [~~LegacyHeaderGroup~~](type-aliases/LegacyHeaderGroup.md) +- [~~LegacyReactTable~~](type-aliases/LegacyReactTable.md) +- [~~LegacyRow~~](type-aliases/LegacyRow.md) +- [~~LegacyTable~~](type-aliases/LegacyTable.md) +- [~~LegacyTableOptions~~](type-aliases/LegacyTableOptions.md) +- [RowModelFactory](type-aliases/RowModelFactory.md) + +## Functions + +- [~~getCoreRowModel~~](functions/getCoreRowModel.md) +- [~~getExpandedRowModel~~](functions/getExpandedRowModel.md) +- [~~getFacetedMinMaxValues~~](functions/getFacetedMinMaxValues.md) +- [~~getFacetedRowModel~~](functions/getFacetedRowModel.md) +- [~~getFacetedUniqueValues~~](functions/getFacetedUniqueValues.md) +- [~~getFilteredRowModel~~](functions/getFilteredRowModel.md) +- [~~getGroupedRowModel~~](functions/getGroupedRowModel.md) +- [~~getPaginationRowModel~~](functions/getPaginationRowModel.md) +- [~~getSortedRowModel~~](functions/getSortedRowModel.md) +- [~~legacyCreateColumnHelper~~](functions/legacyCreateColumnHelper.md) +- [~~useLegacyTable~~](functions/useLegacyTable.md) diff --git a/docs/framework/react/reference/legacy/interfaces/LegacyRowModelOptions.md b/docs/framework/react/reference/legacy/interfaces/LegacyRowModelOptions.md new file mode 100644 index 0000000000..cd82a146c2 --- /dev/null +++ b/docs/framework/react/reference/legacy/interfaces/LegacyRowModelOptions.md @@ -0,0 +1,208 @@ +--- +id: LegacyRowModelOptions +title: LegacyRowModelOptions +--- + +# Interface: LegacyRowModelOptions\ + +Defined in: [useLegacyTable.ts:193](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L193) + +Legacy v8-style row model options + +## Type Parameters + +### TData + +`TData` *extends* `RowData` + +## Properties + +### ~~aggregationFns?~~ + +```ts +optional aggregationFns: AggregationFns; +``` + +Defined in: [useLegacyTable.ts:253](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L253) + +Additional aggregation functions to apply to the table. + +#### Deprecated + +Use `_rowModels.groupedRowModel` with `createGroupedRowModel(aggregationFns)` instead. + +*** + +### ~~filterFns?~~ + +```ts +optional filterFns: FilterFns; +``` + +Defined in: [useLegacyTable.ts:243](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L243) + +Additional filter functions to apply to the table. + +#### Deprecated + +Use `_rowModels.filteredRowModel` with `createFilteredRowModel(filterFns)` instead. + +*** + +### ~~getCoreRowModel?~~ + +```ts +optional getCoreRowModel: RowModelFactory; +``` + +Defined in: [useLegacyTable.ts:198](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L198) + +Returns the core row model for the table. + +#### Deprecated + +This option is no longer needed in v9. The core row model is always created automatically. + +*** + +### ~~getExpandedRowModel?~~ + +```ts +optional getExpandedRowModel: RowModelFactory; +``` + +Defined in: [useLegacyTable.ts:218](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L218) + +Returns the expanded row model for the table. + +#### Deprecated + +Use `_rowModels.expandedRowModel` with `createExpandedRowModel()` instead. + +*** + +### ~~getFacetedMinMaxValues?~~ + +```ts +optional getFacetedMinMaxValues: FacetedMinMaxValuesFactory; +``` + +Defined in: [useLegacyTable.ts:233](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L233) + +Returns the faceted min/max values for a column. + +#### Deprecated + +Use `_rowModels.facetedMinMaxValues` with `createFacetedMinMaxValues()` instead. + +*** + +### ~~getFacetedRowModel?~~ + +```ts +optional getFacetedRowModel: FacetedRowModelFactory; +``` + +Defined in: [useLegacyTable.ts:228](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L228) + +Returns the faceted row model for a column. + +#### Deprecated + +Use `_rowModels.facetedRowModel` with `createFacetedRowModel()` instead. + +*** + +### ~~getFacetedUniqueValues?~~ + +```ts +optional getFacetedUniqueValues: FacetedUniqueValuesFactory; +``` + +Defined in: [useLegacyTable.ts:238](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L238) + +Returns the faceted unique values for a column. + +#### Deprecated + +Use `_rowModels.facetedUniqueValues` with `createFacetedUniqueValues()` instead. + +*** + +### ~~getFilteredRowModel?~~ + +```ts +optional getFilteredRowModel: RowModelFactory; +``` + +Defined in: [useLegacyTable.ts:203](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L203) + +Returns the filtered row model for the table. + +#### Deprecated + +Use `_rowModels.filteredRowModel` with `createFilteredRowModel(filterFns)` instead. + +*** + +### ~~getGroupedRowModel?~~ + +```ts +optional getGroupedRowModel: RowModelFactory; +``` + +Defined in: [useLegacyTable.ts:223](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L223) + +Returns the grouped row model for the table. + +#### Deprecated + +Use `_rowModels.groupedRowModel` with `createGroupedRowModel(aggregationFns)` instead. + +*** + +### ~~getPaginationRowModel?~~ + +```ts +optional getPaginationRowModel: RowModelFactory; +``` + +Defined in: [useLegacyTable.ts:213](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L213) + +Returns the paginated row model for the table. + +#### Deprecated + +Use `_rowModels.paginatedRowModel` with `createPaginatedRowModel()` instead. + +*** + +### ~~getSortedRowModel?~~ + +```ts +optional getSortedRowModel: RowModelFactory; +``` + +Defined in: [useLegacyTable.ts:208](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L208) + +Returns the sorted row model for the table. + +#### Deprecated + +Use `_rowModels.sortedRowModel` with `createSortedRowModel(sortFns)` instead. + +*** + +### ~~sortFns?~~ + +```ts +optional sortFns: SortFns; +``` + +Defined in: [useLegacyTable.ts:248](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L248) + +Additional sort functions to apply to the table. + +#### Deprecated + +Use `_rowModels.sortedRowModel` with `createSortedRowModel(sortFns)` instead. diff --git a/docs/framework/react/reference/legacy/type-aliases/FacetedMinMaxValuesFactory.md b/docs/framework/react/reference/legacy/type-aliases/FacetedMinMaxValuesFactory.md new file mode 100644 index 0000000000..d3ca59bf3a --- /dev/null +++ b/docs/framework/react/reference/legacy/type-aliases/FacetedMinMaxValuesFactory.md @@ -0,0 +1,40 @@ +--- +id: FacetedMinMaxValuesFactory +title: FacetedMinMaxValuesFactory +--- + +# Type Alias: FacetedMinMaxValuesFactory()\ + +```ts +type FacetedMinMaxValuesFactory = (table, columnId) => () => undefined | [number, number]; +``` + +Defined in: [useLegacyTable.ts:177](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L177) + +Faceted min/max values factory function type from v8 API + +## Type Parameters + +### TData + +`TData` *extends* `RowData` + +## Parameters + +### table + +`Table`\<`StockFeatures`, `TData`\> + +### columnId + +`string` + +## Returns + +```ts +(): undefined | [number, number]; +``` + +### Returns + +`undefined` \| \[`number`, `number`\] diff --git a/docs/framework/react/reference/legacy/type-aliases/FacetedRowModelFactory.md b/docs/framework/react/reference/legacy/type-aliases/FacetedRowModelFactory.md new file mode 100644 index 0000000000..6c27a6aa03 --- /dev/null +++ b/docs/framework/react/reference/legacy/type-aliases/FacetedRowModelFactory.md @@ -0,0 +1,40 @@ +--- +id: FacetedRowModelFactory +title: FacetedRowModelFactory +--- + +# Type Alias: FacetedRowModelFactory()\ + +```ts +type FacetedRowModelFactory = (table, columnId) => () => RowModel; +``` + +Defined in: [useLegacyTable.ts:169](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L169) + +Faceted row model factory function type from v8 API + +## Type Parameters + +### TData + +`TData` *extends* `RowData` + +## Parameters + +### table + +`Table`\<`StockFeatures`, `TData`\> + +### columnId + +`string` + +## Returns + +```ts +(): RowModel; +``` + +### Returns + +`RowModel`\<`StockFeatures`, `TData`\> diff --git a/docs/framework/react/reference/legacy/type-aliases/FacetedUniqueValuesFactory.md b/docs/framework/react/reference/legacy/type-aliases/FacetedUniqueValuesFactory.md new file mode 100644 index 0000000000..a73bd23fe8 --- /dev/null +++ b/docs/framework/react/reference/legacy/type-aliases/FacetedUniqueValuesFactory.md @@ -0,0 +1,40 @@ +--- +id: FacetedUniqueValuesFactory +title: FacetedUniqueValuesFactory +--- + +# Type Alias: FacetedUniqueValuesFactory()\ + +```ts +type FacetedUniqueValuesFactory = (table, columnId) => () => Map; +``` + +Defined in: [useLegacyTable.ts:185](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L185) + +Faceted unique values factory function type from v8 API + +## Type Parameters + +### TData + +`TData` *extends* `RowData` + +## Parameters + +### table + +`Table`\<`StockFeatures`, `TData`\> + +### columnId + +`string` + +## Returns + +```ts +(): Map; +``` + +### Returns + +`Map`\<`any`, `number`\> diff --git a/docs/framework/react/reference/legacy/type-aliases/LegacyCell.md b/docs/framework/react/reference/legacy/type-aliases/LegacyCell.md new file mode 100644 index 0000000000..00f35e697b --- /dev/null +++ b/docs/framework/react/reference/legacy/type-aliases/LegacyCell.md @@ -0,0 +1,26 @@ +--- +id: LegacyCell +title: LegacyCell +--- + +# ~~Type Alias: LegacyCell\~~ + +```ts +type LegacyCell = Cell; +``` + +Defined in: [useLegacyTable.ts:307](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L307) + +## Type Parameters + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` = `unknown` + +## Deprecated + +Use Cell with useTable instead. diff --git a/docs/framework/react/reference/legacy/type-aliases/LegacyColumn.md b/docs/framework/react/reference/legacy/type-aliases/LegacyColumn.md new file mode 100644 index 0000000000..4b44ae6069 --- /dev/null +++ b/docs/framework/react/reference/legacy/type-aliases/LegacyColumn.md @@ -0,0 +1,26 @@ +--- +id: LegacyColumn +title: LegacyColumn +--- + +# ~~Type Alias: LegacyColumn\~~ + +```ts +type LegacyColumn = Column; +``` + +Defined in: [useLegacyTable.ts:297](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L297) + +## Type Parameters + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` = `unknown` + +## Deprecated + +Use Column with useTable instead. diff --git a/docs/framework/react/reference/legacy/type-aliases/LegacyColumnDef.md b/docs/framework/react/reference/legacy/type-aliases/LegacyColumnDef.md new file mode 100644 index 0000000000..8ed0d7dcd7 --- /dev/null +++ b/docs/framework/react/reference/legacy/type-aliases/LegacyColumnDef.md @@ -0,0 +1,26 @@ +--- +id: LegacyColumnDef +title: LegacyColumnDef +--- + +# ~~Type Alias: LegacyColumnDef\~~ + +```ts +type LegacyColumnDef = ColumnDef; +``` + +Defined in: [useLegacyTable.ts:327](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L327) + +## Type Parameters + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` = `unknown` + +## Deprecated + +Use ColumnDef with useTable instead. diff --git a/docs/framework/react/reference/legacy/type-aliases/LegacyHeader.md b/docs/framework/react/reference/legacy/type-aliases/LegacyHeader.md new file mode 100644 index 0000000000..c13ad0c498 --- /dev/null +++ b/docs/framework/react/reference/legacy/type-aliases/LegacyHeader.md @@ -0,0 +1,26 @@ +--- +id: LegacyHeader +title: LegacyHeader +--- + +# ~~Type Alias: LegacyHeader\~~ + +```ts +type LegacyHeader = Header; +``` + +Defined in: [useLegacyTable.ts:314](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L314) + +## Type Parameters + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` = `unknown` + +## Deprecated + +Use Header with useTable instead. diff --git a/docs/framework/react/reference/legacy/type-aliases/LegacyHeaderGroup.md b/docs/framework/react/reference/legacy/type-aliases/LegacyHeaderGroup.md new file mode 100644 index 0000000000..c38b276b4b --- /dev/null +++ b/docs/framework/react/reference/legacy/type-aliases/LegacyHeaderGroup.md @@ -0,0 +1,22 @@ +--- +id: LegacyHeaderGroup +title: LegacyHeaderGroup +--- + +# ~~Type Alias: LegacyHeaderGroup\~~ + +```ts +type LegacyHeaderGroup = HeaderGroup; +``` + +Defined in: [useLegacyTable.ts:321](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L321) + +## Type Parameters + +### TData + +`TData` *extends* `RowData` + +## Deprecated + +Use HeaderGroup with useTable instead. diff --git a/docs/framework/react/reference/legacy/type-aliases/LegacyReactTable.md b/docs/framework/react/reference/legacy/type-aliases/LegacyReactTable.md new file mode 100644 index 0000000000..2175cf834d --- /dev/null +++ b/docs/framework/react/reference/legacy/type-aliases/LegacyReactTable.md @@ -0,0 +1,64 @@ +--- +id: LegacyReactTable +title: LegacyReactTable +--- + +# ~~Type Alias: LegacyReactTable\~~ + +```ts +type LegacyReactTable = ReactTable> & object; +``` + +Defined in: [useLegacyTable.ts:275](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L275) + +Legacy table instance type that includes the v8-style `getState()` method. + +## Type Declaration + +### ~~getState()~~ + +```ts +getState: () => TableState; +``` + +Returns the current table state. + +#### Returns + +`TableState`\<`StockFeatures`\> + +#### Deprecated + +In v9, access state directly via `table.state` or use `table.store.state` for the full state. + +### ~~setState()~~ + +```ts +setState: (state) => void; +``` + +Sets the current table state. + +#### Parameters + +##### state + +`TableState`\<`StockFeatures`\> + +#### Returns + +`void` + +#### Deprecated + +In v9, access state directly via `table.baseAtoms` + +## Type Parameters + +### TData + +`TData` *extends* `RowData` + +## Deprecated + +Use `useTable` with explicit state selection instead. diff --git a/docs/framework/react/reference/legacy/type-aliases/LegacyRow.md b/docs/framework/react/reference/legacy/type-aliases/LegacyRow.md new file mode 100644 index 0000000000..64c9f1adb1 --- /dev/null +++ b/docs/framework/react/reference/legacy/type-aliases/LegacyRow.md @@ -0,0 +1,22 @@ +--- +id: LegacyRow +title: LegacyRow +--- + +# ~~Type Alias: LegacyRow\~~ + +```ts +type LegacyRow = Row; +``` + +Defined in: [useLegacyTable.ts:304](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L304) + +## Type Parameters + +### TData + +`TData` *extends* `RowData` + +## Deprecated + +Use Row with useTable instead. diff --git a/docs/framework/react/reference/legacy/type-aliases/LegacyTable.md b/docs/framework/react/reference/legacy/type-aliases/LegacyTable.md new file mode 100644 index 0000000000..0d4757e41b --- /dev/null +++ b/docs/framework/react/reference/legacy/type-aliases/LegacyTable.md @@ -0,0 +1,22 @@ +--- +id: LegacyTable +title: LegacyTable +--- + +# ~~Type Alias: LegacyTable\~~ + +```ts +type LegacyTable = Table; +``` + +Defined in: [useLegacyTable.ts:333](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L333) + +## Type Parameters + +### TData + +`TData` *extends* `RowData` + +## Deprecated + +Use Table with useTable instead. diff --git a/docs/framework/react/reference/legacy/type-aliases/LegacyTableOptions.md b/docs/framework/react/reference/legacy/type-aliases/LegacyTableOptions.md new file mode 100644 index 0000000000..27692999a9 --- /dev/null +++ b/docs/framework/react/reference/legacy/type-aliases/LegacyTableOptions.md @@ -0,0 +1,27 @@ +--- +id: LegacyTableOptions +title: LegacyTableOptions +--- + +# ~~Type Alias: LegacyTableOptions\~~ + +```ts +type LegacyTableOptions = Omit, "_features" | "_rowModels"> & LegacyRowModelOptions; +``` + +Defined in: [useLegacyTable.ts:264](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L264) + +Legacy v8-style table options that work with useLegacyTable. + +This type omits `_features` and `_rowModels` and instead accepts the v8-style +`get*RowModel` function options. + +## Type Parameters + +### TData + +`TData` *extends* `RowData` + +## Deprecated + +This is a compatibility layer for migrating from v8. Use `useTable` with explicit `_features` and `_rowModels` instead. diff --git a/docs/framework/react/reference/legacy/type-aliases/RowModelFactory.md b/docs/framework/react/reference/legacy/type-aliases/RowModelFactory.md new file mode 100644 index 0000000000..fb7d34bffc --- /dev/null +++ b/docs/framework/react/reference/legacy/type-aliases/RowModelFactory.md @@ -0,0 +1,36 @@ +--- +id: RowModelFactory +title: RowModelFactory +--- + +# Type Alias: RowModelFactory()\ + +```ts +type RowModelFactory = (table) => () => RowModel; +``` + +Defined in: [useLegacyTable.ts:162](https://github.com/TanStack/table/blob/main/packages/react-table/src/useLegacyTable.ts#L162) + +Row model factory function type from v8 API + +## Type Parameters + +### TData + +`TData` *extends* `RowData` + +## Parameters + +### table + +`Table`\<`StockFeatures`, `TData`\> + +## Returns + +```ts +(): RowModel; +``` + +### Returns + +`RowModel`\<`StockFeatures`, `TData`\> diff --git a/docs/framework/solid/guide/table-state.md b/docs/framework/solid/guide/table-state.md index f5d2f1a62c..447709d85d 100644 --- a/docs/framework/solid/guide/table-state.md +++ b/docs/framework/solid/guide/table-state.md @@ -2,220 +2,363 @@ title: Table State (Solid) Guide --- +## Examples + +Want to skip to the implementation? Check out these examples: + +- [Basic createTable](../examples/basic-use-table) +- [Basic External Atoms](../examples/basic-external-atoms) +- [Basic External State](../examples/basic-external-state) +- [With TanStack Query](../examples/with-tanstack-query) + ## Table State (Solid) Guide -TanStack Table has a simple underlying internal state management system to store and manage the state of the table. It also lets you selectively pull out any state that you need to manage in your own state management. This guide will walk you through the different ways in which you can interact with and manage the state of the table. +> **If you boil TanStack Table down to one sentence: TanStack Table is a large state-management coordinator for table states.** -### Accessing Table State +Understanding this guide is fundamental to understanding how TanStack Table works and how to interact with it for the best results. + +### Do you need to Manage External State? + +You usually do NOT need to manage table state yourself. If you pass nothing to `initialState`, `atoms`, `state`, or any of the `on[State]Change` table options, TanStack Table will manage its own state internally. + +There will be situations where you need to customize how you interact with the internal table state, or even hoist it up to your own scopes. TanStack Table lets you read, subscribe to, or own the state slices that matter to your app. This guide explains how table state works in Solid, how to read it, and when to use external atoms or external state. + +### State in v9 + +TanStack Table v9 overhauled state management around TanStack Store. TanStack Store uses the `alien-signals` implementation and supports performant derived state. For Solid, the table adapter supplies custom reactivity so table state atoms are backed by Solid primitives. -You do not need to set up anything special in order for the table state to work. If you pass nothing into either `state`, `initialState`, or any of the `on[State]Change` table options, the table will manage its own state internally. You can access any part of this internal state by using the `table.getState()` table instance API. +A table instance has a few state surfaces: -```jsx -const table = createSolidTable({ +- `table.baseAtoms` are the internal writable atoms created from the resolved initial state. +- `table.atoms` are readonly derived atoms exposed per registered state slice. +- `table.store` is a readonly flat TanStack Store derived by putting all of the registered `table.atoms` together. +- `table.state()` is Solid-only selected state. It is the accessor returned from the selector passed as the second argument to `createTable`. + +The Solid adapter provides `solidReactivity(owner)` to the table's `coreReativityFeature`. Core readonly atoms are Solid `createMemo` values and core writable atoms are Solid `createSignal` values. Because atom `.get()` reads through Solid signals and memos, table APIs can be consumed inside Solid computations and update only the computations that read the relevant state. + +### Feature-based State + +State slices are only created for the features that are registered in `_features`. This keeps TanStack Table tree-shakeable and gives TypeScript more accurate state inference. + +```tsx +const _features = tableFeatures({ + rowPaginationFeature, + rowSortingFeature, +}) + +const table = createTable({ + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, columns, get data() { return data() }, - //... }) -console.log(table.getState()) //access the entire internal state -console.log(table.getState().rowSelection) //access just the row selection state +table.atoms.pagination.get() +table.atoms.sorting.get() + +// table.atoms.rowSelection // TypeScript error unless rowSelectionFeature is registered ``` -### Custom Initial State +If `_features` does not include a feature, its state should not be available in `table.atoms`, `table.store.state`, `table.state()`, `initialState`, `state`, or `atoms`. -If all you need to do for certain states is customize their initial default values, you still do not need to manage any of the state yourself. You can simply set values in the `initialState` option of the table instance. +### Accessing Table State -```jsx -const table = createSolidTable({ - columns, - data, - initialState: { - columnOrder: ['age', 'firstName', 'lastName'], //customize the initial column order - columnVisibility: { - id: false //hide the id column by default - }, - expanded: true, //expand all rows by default - sorting: [ - { - id: 'age', - desc: true //sort by age in descending order by default - } - ] - }, - //... -}) -``` +There are two different questions when reading table state: -> **Note**: Only specify each particular state in either `initialState` or `state`, but not both. If you pass in a particular state value to both `initialState` and `state`, the initialized state in `state` will take overwrite any corresponding value in `initialState`. +- Do you only need the current value? +- Or should a Solid computation update when that value changes? -### Controlled State +Use a direct atom or store read for the current value. Use a selector, accessor, or `table.Subscribe` when you want Solid's fine-grained updates. + +#### Reading State Without Subscribing -If you need easy access to the table state in other areas of your application, TanStack Table makes it easy to control and manage any or all of the table state in your own state management system. You can do this by passing in your own state and state management functions to the `state` and `on[State]Change` table options. +The simplest and most performant way to read a current state value is to read the matching atom: -#### Individual Controlled State +```tsx +const pagination = table.atoms.pagination.get() +const sorting = table.atoms.sorting.get() +``` -You can control just the state that you need easy access to. You do NOT have to control all of the table state if you do not need to. It is recommended to only control the state that you need on a case-by-case basis. +You can also read the current flat store snapshot: -In order to control a particular state, you need to both pass in the corresponding `state` value and the `on[State]Change` function to the table instance. +```tsx +const tableState = table.store.state +const pagination = table.store.state.pagination +``` -Let's take filtering, sorting, and pagination as an example in a "manual" server-side data fetching scenario. You can store the filtering, sorting, and pagination state in your own state management, but leave out any other state like column order, column visibility, etc. if your API does not care about those values. +These reads are current-value reads. They only participate in Solid dependency tracking when they are called inside a Solid reactive scope that tracks those reads. If the UI needs to stay reactive to table state changes, use `table.state()`, `table.Subscribe`, or even a `useSelector` hook from TanStack Store. -```jsx -const [columnFilters, setColumnFilters] = createSignal([]) //no default filters -const [sorting, setSorting] = createSignal([{ - id: 'age', - desc: true, //sort by age in descending order by default -}]) -const [pagination, setPagination] = createSignal({ pageIndex: 0, pageSize: 15 }) +#### Reading Reactive State with createTable -//Use our controlled state values to fetch data -const tableQuery = createQuery({ - queryKey: ['users', columnFilters, sorting, pagination], - queryFn: () => fetchUsers(columnFilters, sorting, pagination), - //... -}) +The second argument to `createTable` is a TanStack Store selector. The selected value is exposed as `table.state()`. The default selector selects all registered table state. -const table = createSolidTable({ - columns, - get data() { - return tableQuery.data() - }, - //... - state: { - get columnFilters() { - return columnFilters() //pass controlled state back to the table (overrides internal state) - }, - get sorting() { - return sorting() +```tsx +const table = createTable( + { + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), }, - get pagination() { - return pagination() + columns, + get data() { + return data() }, }, - onColumnFiltersChange: setColumnFilters, //hoist columnFilters state into our own state management - onSortingChange: setSorting, - onPaginationChange: setPagination, -}) -//... + (state) => ({ + pagination: state.pagination, + }), +) + +table.state().pagination +``` + +You can use the selected state in `createMemo`, JSX, or other Solid computations. Those computations update when the selected state changes. + +#### Fine-grained Updates with table.Subscribe + +Use `table.Subscribe` when you want a specific part of the Solid tree to subscribe to a selected table state value. The child function receives a Solid accessor. + +Without a `source` prop, `table.Subscribe` subscribes to `table.store` and requires a selector. With a `source` prop, it can subscribe directly to one atom or store. + +```tsx + ({ + columnFilters: state.columnFilters, + globalFilter: state.globalFilter, + pagination: state.pagination, + })} +> + {() => ( + + + {(row) => {/* ... */}} + + + )} + +``` + +```tsx + rowSelection[row.id]} +> + {(isSelected) => ( + + )} + +``` + +### Setting Table State + +You should almost never need to set table state directly. TanStack Table features expose dedicated APIs for interacting with their state, and those APIs are the safest way to make changes. + +```tsx +table.nextPage() +table.previousPage() +table.setPageIndex(0) +table.setPageSize(25) ``` -#### Fully Controlled State +Use APIs like `table.setSorting(...)`, `table.setColumnFilters(...)`, `column.toggleVisibility()`, or `row.toggleSelected()` instead of manually editing the underlying state object. -Alternatively, you can control the entire table state with the `onStateChange` table option. It will hoist out the entire table state into your own state management system. Be careful with this approach, as you might find that raising some frequently changing state values up a solid tree, like `columnSizingInfo` state`, might cause bad performance issues. +If you only care about setting starting values, use `initialState`. If you want to reset a state slice back to its initial value, use that feature's reset API. -A couple of more tricks may be needed to make this work. If you use the `onStateChange` table option, the initial values of the `state` must be populated with all of the relevant state values for all of the features that you want to use. You can either manually type out all of the initial state values, or use the `table.setOptions` API in a special way as shown below. +If you really do need to write a state slice directly, the low-level write surface for internally owned state is the matching base atom: -```jsx -//create a table instance with default state values -const table = createSolidTable({ +```tsx +table.baseAtoms.pagination.set((old) => ({ + ...old, + pageIndex: 0, +})) +``` + +Direct base atom writes should be rare. If a slice is owned by an external atom passed through `atoms`, write to that external atom instead; `table.atoms.pagination` will read from the external atom, not the internal base atom. + +### Custom Initial State + +If you only need to customize the starting value for some table state, use `initialState`. You still do not need to manage that state yourself. + +`initialState` only applies to registered state slices. It is used to create the table's initial state and is also used by reset APIs such as `table.resetSorting()` or `table.resetPagination()`. Changing the `initialState` object later does not reset table state. + +```tsx +const table = createTable({ + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, columns, get data() { return data() }, - //... Note: `state` values are NOT passed in yet + initialState: { + sorting: [ + { + id: 'age', + desc: true, + }, + ], + pagination: { + pageIndex: 0, + pageSize: 25, + }, + }, }) +``` +> **Note:** Do not provide the same state slice in multiple ownership places unless you intentionally want one to win. For a slice like `pagination`, prefer exactly one of `initialState.pagination`, `atoms.pagination`, or `state.pagination` as the source of truth. External atoms take precedence over external `state`; external `state` syncs into the table's internal base atom. -const [state, setState] = createSignal({ - ...table.initialState, //populate the initial state with all of the default state values from the table instance - pagination: { - pageIndex: 0, - pageSize: 15 //optionally customize the initial pagination state. - } -}) +#### Resetting to Initial State -//Use the table.setOptions API to merge our fully controlled state onto the table instance -table.setOptions(prev => ({ - ...prev, //preserve any other options that we have set up above - get state() { - return state() //our fully controlled state overrides the internal state - }, - onStateChange: setState //any state changes will be pushed up to our own state management -})) +Feature reset APIs reset to `table.initialState` by default. Many reset APIs also accept `true` to reset to that feature's blank/default state instead: + +```tsx +table.resetSorting() +table.resetPagination() +table.resetPagination(true) ``` -### On State Change Callbacks +Slice reset APIs like `resetPagination()` update through that feature's state updater and can update an externally owned atom. The core `table.reset()` API resets the internal base atoms, so do not use it as the primary way to reset state that is owned by external atoms. + +### Controlled State + +If you need easy access to table state in other parts of your application, you can control individual state slices. In v9, external atoms are the recommended way to do this because they preserve the atomic state model and Solid can update computations that read only the relevant slices. + +#### External Atoms + +Use external atoms when the app should own one or more table state slices. Create stable writable atoms with `createAtom`, pass them to `atoms`, and subscribe to them with `useSelector` anywhere else in your app. + +```tsx +import { createAtom, useSelector } from '@tanstack/solid-store' +import { + createTable, + rowPaginationFeature, + tableFeatures, + type PaginationState, +} from '@tanstack/solid-table' + +const _features = tableFeatures({ + rowPaginationFeature, +}) -So far, we have seen the `on[State]Change` and `onStateChange` table options work to "hoist" the table state changes into our own state management. However, there are a few things about these using these options that you should be aware of. +const paginationAtom = createAtom({ + pageIndex: 0, + pageSize: 10, +}) -#### 1. **State Change Callbacks MUST have their corresponding state value in the `state` option**. +const pagination = useSelector(paginationAtom) -Specifying an `on[State]Change` callback tells the table instance that this will be a controlled state. If you do not specify the corresponding `state` value, that state will be "frozen" with its initial value. +const dataQuery = useQuery(() => ({ + queryKey: ['data', pagination()], + queryFn: () => fetchData(pagination()), +})) -```jsx -const [sorting, setSorting] = createSignal([]) -//... -const table = createSolidTable({ +const table = createTable({ + _features, + _rowModels: {}, columns, - data, - //... - state: { - get sorting() { - return sorting() //required because we are using `onSortingChange` - }, + get data() { + return dataQuery.data?.rows ?? [] }, - onSortingChange: setSorting, //makes the `state.sorting` controlled + get rowCount() { + return dataQuery.data?.rowCount + }, + atoms: { + pagination: paginationAtom, + }, + manualPagination: true, }) ``` -#### 2. **Updaters can either be raw values or callback functions**. +When using the `atoms` option for a slice, you do not need to add the matching `on[State]Change` option. -The `on[State]Change` and `onStateChange` callbacks work exactly like the `setState` functions in React (Solid Setters). The updater values can either be a new state value or a callback function that takes the previous state value and returns the new state value. +#### External State -What implications does this have? It means that if you want to add in some extra logic in any of the `on[State]Change` callbacks, you can do so, but you need to check whether or not the new incoming updater value is a function or value. +The classic `state` plus `on[State]Change` pattern is still supported. This can be convenient for simple integrations or when migrating v8 code, but it is less atomic than external atoms. -```jsx -const [sorting, setSorting] = createSignal([]) -const [pagination, setPagination] = createSignal({ pageIndex: 0, pageSize: 10 }) +```tsx +const [sorting, setSorting] = createSignal([]) +const [pagination, setPagination] = createSignal({ + pageIndex: 0, + pageSize: 10, +}) -const table = createSolidTable({ - get columns() { - return columns() +const table = createTable({ + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), }, + columns, get data() { return data() }, - //... state: { - get pagination() { - return pagination() - }, get sorting() { return sorting() }, - } - //syntax 1 - onPaginationChange: (updater) => { - setPagination(old => { - const newPaginationValue = updater instanceof Function ? updater(old) : updater - //do something with the new pagination value - //... - return newPaginationValue - }) + get pagination() { + return pagination() + }, }, - //syntax 2 - onSortingChange: (updater) => { - const newSortingValue = updater instanceof Function ? updater(sorting) : updater - //do something with the new sorting value - //... - setSorting(updater) //normal state update - } + onSortingChange: setSorting, + onPaginationChange: setPagination, }) ``` +The v8-style `onStateChange` option is no longer part of the v9 `createTable` state model. v9 encourages keeping table state slices atomic and separated for performance. + +##### On State Change Callbacks + +The `on[State]Change` callbacks are useful when you are controlling a matching slice through the `state` option. They work like setters: an updater can be a raw value or a function that receives the previous value and returns the next value. + +If you provide an `on[State]Change` callback, also provide the corresponding value in `state`. For example, `onSortingChange` should be paired with `state.sorting`. + +```tsx +onPaginationChange: (updater) => { + setPagination((old) => { + const next = updater instanceof Function ? updater(old) : updater + + // side effects or validation can happen here + + return next + }) +} +``` + ### State Types -All complex states in TanStack Table have their own TypeScript types that you can import and use. This can be handy for ensuring that you are using the correct data structures and properties for the state values that you are controlling. +Most complex states in TanStack Table have their own TypeScript types that you can import and use. ```tsx -import { createSolidTable, type SortingState } from '@tanstack/solid-table' -//... -const [sorting, setSorting] = createSignal([ +import { + createTable, + type PaginationState, + type RowSelectionState, + type SortingState, + type TableState, +} from '@tanstack/solid-table' + +const [sorting, setSorting] = createSignal([ { - id: 'age', //you should get autocomplete for the `id` and `desc` properties + id: 'age', desc: true, - } + }, ]) -``` \ No newline at end of file +``` + +`TableState` is inferred from the features registered on that table: + +```tsx +type MyTableState = TableState +``` diff --git a/docs/framework/solid/reference/functions/FlexRender-1.md b/docs/framework/solid/reference/functions/FlexRender-1.md new file mode 100644 index 0000000000..acd8d73b31 --- /dev/null +++ b/docs/framework/solid/reference/functions/FlexRender-1.md @@ -0,0 +1,54 @@ +--- +id: FlexRender +title: FlexRender +--- + +# Function: FlexRender() + +```ts +function FlexRender(props): Element; +``` + +Defined in: [FlexRender.tsx:77](https://github.com/TanStack/table/blob/main/packages/solid-table/src/FlexRender.tsx#L77) + +Simplified component wrapper of `flexRender`. Use this utility component to render headers, cells, or footers with custom markup. +Only one prop (`cell`, `header`, or `footer`) may be passed. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### props + +[`FlexRenderProps`](../type-aliases/FlexRenderProps.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`Element` + +## Example + +```tsx + + + +``` + +This replaces calling `flexRender` directly like this: +```tsx +flexRender(cell.column.columnDef.cell, cell.getContext()) +flexRender(header.column.columnDef.header, header.getContext()) +flexRender(footer.column.columnDef.footer, footer.getContext()) +``` diff --git a/docs/framework/solid/reference/functions/createTable.md b/docs/framework/solid/reference/functions/createTable.md new file mode 100644 index 0000000000..02a6d3fd5a --- /dev/null +++ b/docs/framework/solid/reference/functions/createTable.md @@ -0,0 +1,63 @@ +--- +id: createTable +title: createTable +--- + +# Function: createTable() + +```ts +function createTable(tableOptions, selector?): SolidTable; +``` + +Defined in: [createTable.ts:99](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTable.ts#L99) + +Creates a Solid table instance backed by Solid-aware TanStack Store atoms. + +The optional selector projects from `table.store`; the selected value is +exposed as the `table.state` accessor. Table APIs and atom reads participate +in Solid dependency tracking, so computations that read a specific slice can +update without invalidating unrelated UI. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TSelected + +`TSelected` = `TableState`\<`TFeatures`\> + +## Parameters + +### tableOptions + +`TableOptions`\<`TFeatures`, `TData`\> + +### selector? + +(`state`) => `TSelected` + +## Returns + +[`SolidTable`](../type-aliases/SolidTable.md)\<`TFeatures`, `TData`, `TSelected`\> + +## Example + +```tsx +const table = createTable( + { + _features, + _rowModels: {}, + columns, + data, + }, + (state) => ({ pagination: state.pagination }), +) + +table.state().pagination +``` diff --git a/docs/framework/solid/reference/functions/createTableHook.md b/docs/framework/solid/reference/functions/createTableHook.md new file mode 100644 index 0000000000..3fc2b9207e --- /dev/null +++ b/docs/framework/solid/reference/functions/createTableHook.md @@ -0,0 +1,329 @@ +--- +id: createTableHook +title: createTableHook +--- + +# Function: createTableHook() + +```ts +function createTableHook(__namedParameters): object; +``` + +Defined in: [createTableHook.tsx:611](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L611) + +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 + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TTableComponents + +`TTableComponents` *extends* `Record`\<`string`, [`ComponentType`](../type-aliases/ComponentType.md)\<`any`\>\> + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](../type-aliases/ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](../type-aliases/ComponentType.md)\<`any`\>\> + +## Parameters + +### \_\_namedParameters + +[`CreateTableHookOptions`](../type-aliases/CreateTableHookOptions.md)\<`TFeatures`, `TTableComponents`, `TCellComponents`, `THeaderComponents`\> + +## Returns + +### appFeatures + +```ts +appFeatures: TFeatures; +``` + +### createAppColumnHelper() + +```ts +createAppColumnHelper: () => AppColumnHelper; +``` + +Create a column helper pre-bound to the features and components configured in this table hook. +The cell, header, and footer contexts include pre-bound components (e.g., `cell.TextCell`). + +#### Type Parameters + +##### TData + +`TData` *extends* `RowData` + +#### Returns + +[`AppColumnHelper`](../type-aliases/AppColumnHelper.md)\<`TFeatures`, `TData`, `TCellComponents`, `THeaderComponents`\> + +#### Example + +```tsx +const columnHelper = createAppColumnHelper() + +const columns = [ + columnHelper.accessor('firstName', { + header: 'First Name', + cell: ({ cell }) => , // cell has pre-bound components! + }), + columnHelper.accessor('age', { + header: 'Age', + cell: ({ cell }) => , + }), +] +``` + +### createAppTable() + +```ts +createAppTable: (tableOptions, selector?) => AppSolidTable; +``` + +Enhanced useTable hook that returns a table with App wrapper components +and pre-bound tableComponents attached directly to the table object. + +Default options from createTableHook are automatically merged with +the options passed here. Options passed here take precedence. + +TFeatures is already known from the createTableHook call; TData is inferred from the data prop. + +#### Type Parameters + +##### TData + +`TData` *extends* `RowData` + +##### TSelected + +`TSelected` = `TableState`\<`TFeatures`\> + +#### Parameters + +##### tableOptions + +`Omit`\<`TableOptions`\<`TFeatures`, `TData`\>, `"_features"` \| `"_rowModels"`\> + +##### selector? + +(`state`) => `TSelected` + +#### Returns + +[`AppSolidTable`](../type-aliases/AppSolidTable.md)\<`TFeatures`, `TData`, `TSelected`, `TTableComponents`, `TCellComponents`, `THeaderComponents`\> + +### useCellContext() + +```ts +useCellContext: () => Cell; +``` + +Access the cell instance from within an `AppCell` wrapper. +Use this in custom `cellComponents` passed to `createTableHook`. +TFeatures is already known from the createTableHook call. + +#### Type Parameters + +##### TValue + +`TValue` *extends* `unknown` = `unknown` + +#### Returns + +`Cell`\<`TFeatures`, `any`, `TValue`\> + +#### Example + +```tsx +function TextCell() { + const cell = useCellContext() + return {cell.getValue()} +} + +function NumberCell({ format }: { format?: Intl.NumberFormatOptions }) { + const cell = useCellContext() + return {cell.getValue().toLocaleString(undefined, format)} +} +``` + +### useHeaderContext() + +```ts +useHeaderContext: () => Header; +``` + +Access the header instance from within an `AppHeader` or `AppFooter` wrapper. +Use this in custom `headerComponents` passed to `createTableHook`. +TFeatures is already known from the createTableHook call. + +#### Type Parameters + +##### TValue + +`TValue` *extends* `unknown` = `unknown` + +#### Returns + +`Header`\<`TFeatures`, `any`, `TValue`\> + +#### Example + +```tsx +function SortIndicator() { + const header = useHeaderContext() + const sorted = header.column.getIsSorted() + return sorted === 'asc' ? '🔼' : sorted === 'desc' ? '🔽' : null +} + +function ColumnFilter() { + const header = useHeaderContext() + if (!header.column.getCanFilter()) return null + return ( + header.column.setFilterValue(e.target.value)} + placeholder="Filter..." + /> + ) +} +``` + +### useTableContext() + +```ts +useTableContext: () => SolidTable; +``` + +Access the table instance from within an `AppTable` wrapper. +Use this in custom `tableComponents` passed to `createTableHook`. +TFeatures is already known from the createTableHook call. + +#### Type Parameters + +##### TData + +`TData` *extends* `RowData` = `RowData` + +#### Returns + +[`SolidTable`](../type-aliases/SolidTable.md)\<`TFeatures`, `TData`\> + +#### Example + +```tsx +function PaginationControls() { + const table = useTableContext() + return ( + s.pagination}> + {(pagination) => ( +
+ + Page {pagination.pageIndex + 1} + +
+ )} +
+ ) +} +``` + +## Example + +```tsx +// hooks/table.ts +export const { + createAppTable, + createAppColumnHelper, + useTableContext, + useCellContext, + useHeaderContext, +} = createTableHook({ + _features: tableFeatures({ + rowPaginationFeature, + rowSortingFeature, + columnFilteringFeature, + }), + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + filteredRowModel: createFilteredRowModel(filterFns), + }, + tableComponents: { PaginationControls, RowCount }, + cellComponents: { TextCell, NumberCell }, + headerComponents: { SortIndicator, ColumnFilter }, +}) + +// Create column helper with TFeatures already bound +const columnHelper = createAppColumnHelper() + +// components/table-components.tsx +function PaginationControls() { + const table = useTableContext() // TFeatures already known! + return s.pagination}>... +} + +// features/users.tsx +function UsersTable({ data }: { data: Person[] }) { + const table = createAppTable({ + columns, + data, // TData inferred from Person[] + }) + + return ( + + + + + {(headerGroup) => ( + + + {(h) => ( + + {(header) => ( + + )} + + )} + + + )} + + + + + {(row) => ( + + + {(c) => ( + + {(cell) => } + + )} + + + )} + + +
+ + +
+ +
+ ) +} +``` diff --git a/docs/framework/solid/reference/functions/flexRender.md b/docs/framework/solid/reference/functions/flexRender.md new file mode 100644 index 0000000000..76a35f74d1 --- /dev/null +++ b/docs/framework/solid/reference/functions/flexRender.md @@ -0,0 +1,44 @@ +--- +id: flexRender +title: flexRender +--- + +# Function: flexRender() + +```ts +function flexRender(Comp, props): Element; +``` + +Defined in: [FlexRender.tsx:23](https://github.com/TanStack/table/blob/main/packages/solid-table/src/FlexRender.tsx#L23) + +Renders a Solid table template value with the provided context props. + +Use this for custom header, cell, or footer renderers when you need the +lower-level function form. Most Solid UIs can use the `FlexRender` component +instead. + +## Type Parameters + +### TProps + +`TProps` + +## Parameters + +### Comp + +`Element` | (`_props`) => `Element` + +### props + +`TProps` + +## Returns + +`Element` + +## Example + +```tsx +flexRender(cell.column.columnDef.cell, cell.getContext()) +``` diff --git a/docs/framework/solid/reference/index.md b/docs/framework/solid/reference/index.md new file mode 100644 index 0000000000..20748fc535 --- /dev/null +++ b/docs/framework/solid/reference/index.md @@ -0,0 +1,41 @@ +--- +id: "@tanstack/solid-table" +title: "@tanstack/solid-table" +--- + +# @tanstack/solid-table + +## Interfaces + +- [AppCellComponent](interfaces/AppCellComponent.md) +- [AppCellPropsWithoutSelector](interfaces/AppCellPropsWithoutSelector.md) +- [AppCellPropsWithSelector](interfaces/AppCellPropsWithSelector.md) +- [AppHeaderComponent](interfaces/AppHeaderComponent.md) +- [AppHeaderPropsWithoutSelector](interfaces/AppHeaderPropsWithoutSelector.md) +- [AppHeaderPropsWithSelector](interfaces/AppHeaderPropsWithSelector.md) +- [AppTableComponent](interfaces/AppTableComponent.md) +- [AppTablePropsWithoutSelector](interfaces/AppTablePropsWithoutSelector.md) +- [AppTablePropsWithSelector](interfaces/AppTablePropsWithSelector.md) + +## Type Aliases + +- [AppCellContext](type-aliases/AppCellContext.md) +- [AppColumnDefBase](type-aliases/AppColumnDefBase.md) +- [AppColumnDefTemplate](type-aliases/AppColumnDefTemplate.md) +- [AppColumnHelper](type-aliases/AppColumnHelper.md) +- [AppDisplayColumnDef](type-aliases/AppDisplayColumnDef.md) +- [AppGroupColumnDef](type-aliases/AppGroupColumnDef.md) +- [AppHeaderContext](type-aliases/AppHeaderContext.md) +- [AppSolidTable](type-aliases/AppSolidTable.md) +- [ComponentType](type-aliases/ComponentType.md) +- [CreateTableHookOptions](type-aliases/CreateTableHookOptions.md) +- [FlexRenderProps](type-aliases/FlexRenderProps.md) +- [SolidTable](type-aliases/SolidTable.md) +- [SubscribeSource](type-aliases/SubscribeSource.md) + +## Functions + +- [createTable](functions/createTable.md) +- [createTableHook](functions/createTableHook.md) +- [flexRender](functions/flexRender.md) +- [FlexRender](functions/FlexRender-1.md) diff --git a/docs/framework/solid/reference/interfaces/AppCellComponent.md b/docs/framework/solid/reference/interfaces/AppCellComponent.md new file mode 100644 index 0000000000..97a96782dc --- /dev/null +++ b/docs/framework/solid/reference/interfaces/AppCellComponent.md @@ -0,0 +1,80 @@ +--- +id: AppCellComponent +title: AppCellComponent +--- + +# Interface: AppCellComponent()\ + +Defined in: [createTableHook.tsx:368](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L368) + +Component type for AppCell - wraps a cell and provides cell context with optional Subscribe + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](../type-aliases/ComponentType.md)\<`any`\>\> + +## Call Signature + +```ts +AppCellComponent(props): Element; +``` + +Defined in: [createTableHook.tsx:373](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L373) + +Component type for AppCell - wraps a cell and provides cell context with optional Subscribe + +### Type Parameters + +#### TValue + +`TValue` *extends* `unknown` = `unknown` + +### Parameters + +#### props + +[`AppCellPropsWithoutSelector`](AppCellPropsWithoutSelector.md)\<`TFeatures`, `TData`, `TValue`, `TCellComponents`\> + +### Returns + +`Element` + +## Call Signature + +```ts +AppCellComponent(props): Element; +``` + +Defined in: [createTableHook.tsx:381](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L381) + +Component type for AppCell - wraps a cell and provides cell context with optional Subscribe + +### Type Parameters + +#### TValue + +`TValue` *extends* `unknown` = `unknown` + +#### TSelected + +`TSelected` = `unknown` + +### Parameters + +#### props + +[`AppCellPropsWithSelector`](AppCellPropsWithSelector.md)\<`TFeatures`, `TData`, `TValue`, `TCellComponents`, `TSelected`\> + +### Returns + +`Element` diff --git a/docs/framework/solid/reference/interfaces/AppCellPropsWithSelector.md b/docs/framework/solid/reference/interfaces/AppCellPropsWithSelector.md new file mode 100644 index 0000000000..cab29a5142 --- /dev/null +++ b/docs/framework/solid/reference/interfaces/AppCellPropsWithSelector.md @@ -0,0 +1,86 @@ +--- +id: AppCellPropsWithSelector +title: AppCellPropsWithSelector +--- + +# Interface: AppCellPropsWithSelector\ + +Defined in: [createTableHook.tsx:313](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L313) + +Props for AppCell component - with selector + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](../type-aliases/ComponentType.md)\<`any`\>\> + +### TSelected + +`TSelected` + +## Properties + +### cell + +```ts +cell: Cell; +``` + +Defined in: [createTableHook.tsx:320](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L320) + +*** + +### children() + +```ts +children: (cell, state) => Element; +``` + +Defined in: [createTableHook.tsx:321](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L321) + +#### Parameters + +##### cell + +`Cell_Cell`\<`TFeatures`, `TData`, `TValue`\> & `UnionToIntersection`\<`"columnGroupingFeature"` *extends* keyof `TFeatures` ? `Cell_ColumnGrouping` : `never`\> & `UnionToIntersection`\<\{ \[K in string \| number \| symbol\]: K extends "coreReativityFeature" ? never : TFeatures\[K\] extends TableFeature\ ? "Cell" extends keyof FeatureConstructorOptions ? FeatureConstructorOptions\[keyof (...) & "Cell"\] : never : any \}\[keyof `TFeatures`\]\> & `Cell_Plugins`\<`TFeatures`, `TData`, `TValue`\> & `TCellComponents` & `object` + +##### state + +`Accessor`\<`TSelected`\> + +#### Returns + +`Element` + +*** + +### selector() + +```ts +selector: (state) => TSelected; +``` + +Defined in: [createTableHook.tsx:326](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L326) + +#### Parameters + +##### state + +`TableState`\<`TFeatures`\> + +#### Returns + +`TSelected` diff --git a/docs/framework/solid/reference/interfaces/AppCellPropsWithoutSelector.md b/docs/framework/solid/reference/interfaces/AppCellPropsWithoutSelector.md new file mode 100644 index 0000000000..273ed03135 --- /dev/null +++ b/docs/framework/solid/reference/interfaces/AppCellPropsWithoutSelector.md @@ -0,0 +1,68 @@ +--- +id: AppCellPropsWithoutSelector +title: AppCellPropsWithoutSelector +--- + +# Interface: AppCellPropsWithoutSelector\ + +Defined in: [createTableHook.tsx:296](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L296) + +Props for AppCell component - without selector + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](../type-aliases/ComponentType.md)\<`any`\>\> + +## Properties + +### cell + +```ts +cell: Cell; +``` + +Defined in: [createTableHook.tsx:302](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L302) + +*** + +### children() + +```ts +children: (cell) => Element; +``` + +Defined in: [createTableHook.tsx:303](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L303) + +#### Parameters + +##### cell + +`Cell_Cell`\<`TFeatures`, `TData`, `TValue`\> & `UnionToIntersection`\<`"columnGroupingFeature"` *extends* keyof `TFeatures` ? `Cell_ColumnGrouping` : `never`\> & `UnionToIntersection`\<\{ \[K in string \| number \| symbol\]: K extends "coreReativityFeature" ? never : TFeatures\[K\] extends TableFeature\ ? "Cell" extends keyof FeatureConstructorOptions ? FeatureConstructorOptions\[keyof (...) & "Cell"\] : never : any \}\[keyof `TFeatures`\]\> & `Cell_Plugins`\<`TFeatures`, `TData`, `TValue`\> & `TCellComponents` & `object` + +#### Returns + +`Element` + +*** + +### selector? + +```ts +optional selector: undefined; +``` + +Defined in: [createTableHook.tsx:307](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L307) diff --git a/docs/framework/solid/reference/interfaces/AppHeaderComponent.md b/docs/framework/solid/reference/interfaces/AppHeaderComponent.md new file mode 100644 index 0000000000..58275d620d --- /dev/null +++ b/docs/framework/solid/reference/interfaces/AppHeaderComponent.md @@ -0,0 +1,80 @@ +--- +id: AppHeaderComponent +title: AppHeaderComponent +--- + +# Interface: AppHeaderComponent()\ + +Defined in: [createTableHook.tsx:395](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L395) + +Component type for AppHeader/AppFooter - wraps a header and provides header context with optional Subscribe + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](../type-aliases/ComponentType.md)\<`any`\>\> + +## Call Signature + +```ts +AppHeaderComponent(props): Element; +``` + +Defined in: [createTableHook.tsx:400](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L400) + +Component type for AppHeader/AppFooter - wraps a header and provides header context with optional Subscribe + +### Type Parameters + +#### TValue + +`TValue` *extends* `unknown` = `unknown` + +### Parameters + +#### props + +[`AppHeaderPropsWithoutSelector`](AppHeaderPropsWithoutSelector.md)\<`TFeatures`, `TData`, `TValue`, `THeaderComponents`\> + +### Returns + +`Element` + +## Call Signature + +```ts +AppHeaderComponent(props): Element; +``` + +Defined in: [createTableHook.tsx:408](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L408) + +Component type for AppHeader/AppFooter - wraps a header and provides header context with optional Subscribe + +### Type Parameters + +#### TValue + +`TValue` *extends* `unknown` = `unknown` + +#### TSelected + +`TSelected` = `unknown` + +### Parameters + +#### props + +[`AppHeaderPropsWithSelector`](AppHeaderPropsWithSelector.md)\<`TFeatures`, `TData`, `TValue`, `THeaderComponents`, `TSelected`\> + +### Returns + +`Element` diff --git a/docs/framework/solid/reference/interfaces/AppHeaderPropsWithSelector.md b/docs/framework/solid/reference/interfaces/AppHeaderPropsWithSelector.md new file mode 100644 index 0000000000..f665eba0e4 --- /dev/null +++ b/docs/framework/solid/reference/interfaces/AppHeaderPropsWithSelector.md @@ -0,0 +1,88 @@ +--- +id: AppHeaderPropsWithSelector +title: AppHeaderPropsWithSelector +--- + +# Interface: AppHeaderPropsWithSelector\ + +Defined in: [createTableHook.tsx:349](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L349) + +Props for AppHeader/AppFooter component - with selector + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](../type-aliases/ComponentType.md)\<`any`\>\> + +### TSelected + +`TSelected` + +## Properties + +### children() + +```ts +children: (header, state) => Element; +``` + +Defined in: [createTableHook.tsx:357](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L357) + +#### Parameters + +##### header + +`Header_Core`\<`TFeatures`, `TData`, `TValue`\> & `UnionToIntersection`\< + \| `"columnSizingFeature"` *extends* keyof `TFeatures` ? `Header_ColumnSizing` : `never` + \| `"columnResizingFeature"` *extends* keyof `TFeatures` ? `Header_ColumnResizing` : `never`\> & `UnionToIntersection`\<\{ \[K in string \| number \| symbol\]: K extends "coreReativityFeature" ? never : TFeatures\[K\] extends TableFeature\ ? "Header" extends keyof FeatureConstructorOptions ? FeatureConstructorOptions\[keyof (...) & "Header"\] : never : any \}\[keyof `TFeatures`\]\> & `Header_Plugins`\<`TFeatures`, `TData`, `TValue`\> & `THeaderComponents` & `object` + +##### state + +`Accessor`\<`TSelected`\> + +#### Returns + +`Element` + +*** + +### header + +```ts +header: Header; +``` + +Defined in: [createTableHook.tsx:356](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L356) + +*** + +### selector() + +```ts +selector: (state) => TSelected; +``` + +Defined in: [createTableHook.tsx:362](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L362) + +#### Parameters + +##### state + +`TableState`\<`TFeatures`\> + +#### Returns + +`TSelected` diff --git a/docs/framework/solid/reference/interfaces/AppHeaderPropsWithoutSelector.md b/docs/framework/solid/reference/interfaces/AppHeaderPropsWithoutSelector.md new file mode 100644 index 0000000000..6cde73cdff --- /dev/null +++ b/docs/framework/solid/reference/interfaces/AppHeaderPropsWithoutSelector.md @@ -0,0 +1,70 @@ +--- +id: AppHeaderPropsWithoutSelector +title: AppHeaderPropsWithoutSelector +--- + +# Interface: AppHeaderPropsWithoutSelector\ + +Defined in: [createTableHook.tsx:332](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L332) + +Props for AppHeader/AppFooter component - without selector + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](../type-aliases/ComponentType.md)\<`any`\>\> + +## Properties + +### children() + +```ts +children: (header) => Element; +``` + +Defined in: [createTableHook.tsx:339](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L339) + +#### Parameters + +##### header + +`Header_Core`\<`TFeatures`, `TData`, `TValue`\> & `UnionToIntersection`\< + \| `"columnSizingFeature"` *extends* keyof `TFeatures` ? `Header_ColumnSizing` : `never` + \| `"columnResizingFeature"` *extends* keyof `TFeatures` ? `Header_ColumnResizing` : `never`\> & `UnionToIntersection`\<\{ \[K in string \| number \| symbol\]: K extends "coreReativityFeature" ? never : TFeatures\[K\] extends TableFeature\ ? "Header" extends keyof FeatureConstructorOptions ? FeatureConstructorOptions\[keyof (...) & "Header"\] : never : any \}\[keyof `TFeatures`\]\> & `Header_Plugins`\<`TFeatures`, `TData`, `TValue`\> & `THeaderComponents` & `object` + +#### Returns + +`Element` + +*** + +### header + +```ts +header: Header; +``` + +Defined in: [createTableHook.tsx:338](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L338) + +*** + +### selector? + +```ts +optional selector: undefined; +``` + +Defined in: [createTableHook.tsx:343](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L343) diff --git a/docs/framework/solid/reference/interfaces/AppTableComponent.md b/docs/framework/solid/reference/interfaces/AppTableComponent.md new file mode 100644 index 0000000000..57e3eed2ee --- /dev/null +++ b/docs/framework/solid/reference/interfaces/AppTableComponent.md @@ -0,0 +1,62 @@ +--- +id: AppTableComponent +title: AppTableComponent +--- + +# Interface: AppTableComponent()\ + +Defined in: [createTableHook.tsx:422](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L422) + +Component type for AppTable - root wrapper with optional Subscribe + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +## Call Signature + +```ts +AppTableComponent(props): Element; +``` + +Defined in: [createTableHook.tsx:423](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L423) + +Component type for AppTable - root wrapper with optional Subscribe + +### Parameters + +#### props + +[`AppTablePropsWithoutSelector`](AppTablePropsWithoutSelector.md) + +### Returns + +`Element` + +## Call Signature + +```ts +AppTableComponent(props): Element; +``` + +Defined in: [createTableHook.tsx:424](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L424) + +Component type for AppTable - root wrapper with optional Subscribe + +### Type Parameters + +#### TSelected + +`TSelected` + +### Parameters + +#### props + +[`AppTablePropsWithSelector`](AppTablePropsWithSelector.md)\<`TFeatures`, `TSelected`\> + +### Returns + +`Element` diff --git a/docs/framework/solid/reference/interfaces/AppTablePropsWithSelector.md b/docs/framework/solid/reference/interfaces/AppTablePropsWithSelector.md new file mode 100644 index 0000000000..fc67006634 --- /dev/null +++ b/docs/framework/solid/reference/interfaces/AppTablePropsWithSelector.md @@ -0,0 +1,60 @@ +--- +id: AppTablePropsWithSelector +title: AppTablePropsWithSelector +--- + +# Interface: AppTablePropsWithSelector\ + +Defined in: [createTableHook.tsx:285](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L285) + +Props for AppTable component - with selector + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TSelected + +`TSelected` + +## Properties + +### children() + +```ts +children: (state) => Element; +``` + +Defined in: [createTableHook.tsx:289](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L289) + +#### Parameters + +##### state + +`Accessor`\<`TSelected`\> + +#### Returns + +`Element` + +*** + +### selector() + +```ts +selector: (state) => TSelected; +``` + +Defined in: [createTableHook.tsx:290](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L290) + +#### Parameters + +##### state + +`TableState`\<`TFeatures`\> + +#### Returns + +`TSelected` diff --git a/docs/framework/solid/reference/interfaces/AppTablePropsWithoutSelector.md b/docs/framework/solid/reference/interfaces/AppTablePropsWithoutSelector.md new file mode 100644 index 0000000000..02194c4cdf --- /dev/null +++ b/docs/framework/solid/reference/interfaces/AppTablePropsWithoutSelector.md @@ -0,0 +1,30 @@ +--- +id: AppTablePropsWithoutSelector +title: AppTablePropsWithoutSelector +--- + +# Interface: AppTablePropsWithoutSelector + +Defined in: [createTableHook.tsx:277](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L277) + +Props for AppTable component - without selector + +## Properties + +### children + +```ts +children: Element; +``` + +Defined in: [createTableHook.tsx:278](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L278) + +*** + +### selector? + +```ts +optional selector: undefined; +``` + +Defined in: [createTableHook.tsx:279](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L279) diff --git a/docs/framework/solid/reference/type-aliases/AppCellContext.md b/docs/framework/solid/reference/type-aliases/AppCellContext.md new file mode 100644 index 0000000000..3d5fb1e6f2 --- /dev/null +++ b/docs/framework/solid/reference/type-aliases/AppCellContext.md @@ -0,0 +1,105 @@ +--- +id: AppCellContext +title: AppCellContext +--- + +# Type Alias: AppCellContext\ + +```ts +type AppCellContext = object; +``` + +Defined in: [createTableHook.tsx:41](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L41) + +Enhanced CellContext with pre-bound cell components. +The `cell` property includes the registered cellComponents. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +## Properties + +### cell + +```ts +cell: Cell & TCellComponents & object; +``` + +Defined in: [createTableHook.tsx:47](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L47) + +#### Type Declaration + +##### FlexRender() + +```ts +FlexRender: () => JSXElement; +``` + +###### Returns + +`JSXElement` + +*** + +### column + +```ts +column: Column; +``` + +Defined in: [createTableHook.tsx:49](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L49) + +*** + +### getValue + +```ts +getValue: CellContext["getValue"]; +``` + +Defined in: [createTableHook.tsx:50](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L50) + +*** + +### renderValue + +```ts +renderValue: CellContext["renderValue"]; +``` + +Defined in: [createTableHook.tsx:51](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L51) + +*** + +### row + +```ts +row: Row; +``` + +Defined in: [createTableHook.tsx:52](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L52) + +*** + +### table + +```ts +table: Table; +``` + +Defined in: [createTableHook.tsx:53](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L53) diff --git a/docs/framework/solid/reference/type-aliases/AppColumnDefBase.md b/docs/framework/solid/reference/type-aliases/AppColumnDefBase.md new file mode 100644 index 0000000000..d1be49650b --- /dev/null +++ b/docs/framework/solid/reference/type-aliases/AppColumnDefBase.md @@ -0,0 +1,56 @@ +--- +id: AppColumnDefBase +title: AppColumnDefBase +--- + +# Type Alias: AppColumnDefBase\ + +```ts +type AppColumnDefBase = Omit, "cell" | "header" | "footer"> & object; +``` + +Defined in: [createTableHook.tsx:86](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L86) + +Enhanced column definition base with pre-bound components in cell/header/footer contexts. + +## Type Declaration + +### cell? + +```ts +optional cell: AppColumnDefTemplate>; +``` + +### footer? + +```ts +optional footer: AppColumnDefTemplate>; +``` + +### header? + +```ts +optional header: AppColumnDefTemplate>; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> diff --git a/docs/framework/solid/reference/type-aliases/AppColumnDefTemplate.md b/docs/framework/solid/reference/type-aliases/AppColumnDefTemplate.md new file mode 100644 index 0000000000..7988901aef --- /dev/null +++ b/docs/framework/solid/reference/type-aliases/AppColumnDefTemplate.md @@ -0,0 +1,20 @@ +--- +id: AppColumnDefTemplate +title: AppColumnDefTemplate +--- + +# Type Alias: AppColumnDefTemplate\ + +```ts +type AppColumnDefTemplate = string | (props) => any; +``` + +Defined in: [createTableHook.tsx:79](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L79) + +Template type for column definitions that can be a string or a function. + +## Type Parameters + +### TProps + +`TProps` *extends* `object` diff --git a/docs/framework/solid/reference/type-aliases/AppColumnHelper.md b/docs/framework/solid/reference/type-aliases/AppColumnHelper.md new file mode 100644 index 0000000000..be559da935 --- /dev/null +++ b/docs/framework/solid/reference/type-aliases/AppColumnHelper.md @@ -0,0 +1,144 @@ +--- +id: AppColumnHelper +title: AppColumnHelper +--- + +# Type Alias: AppColumnHelper\ + +```ts +type AppColumnHelper = object; +``` + +Defined in: [createTableHook.tsx:162](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L162) + +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 Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +## Properties + +### accessor() + +```ts +accessor: (accessor, column) => TAccessor extends AccessorFn ? AccessorFnColumnDef : AccessorKeyColumnDef; +``` + +Defined in: [createTableHook.tsx:172](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L172) + +Creates a data column definition with an accessor key or function. +The cell, header, and footer contexts include pre-bound components. + +#### Type Parameters + +##### TAccessor + +`TAccessor` *extends* `AccessorFn`\<`TData`\> \| `DeepKeys`\<`TData`\> + +##### TValue + +`TValue` *extends* `TAccessor` *extends* `AccessorFn`\<`TData`, infer TReturn\> ? `TReturn` : `TAccessor` *extends* `DeepKeys`\<`TData`\> ? `DeepValue`\<`TData`, `TAccessor`\> : `never` + +#### Parameters + +##### accessor + +`TAccessor` + +##### column + +`TAccessor` *extends* `AccessorFn`\<`TData`\> ? [`AppColumnDefBase`](AppColumnDefBase.md)\<`TFeatures`, `TData`, `TValue`, `TCellComponents`, `THeaderComponents`\> & `object` : [`AppColumnDefBase`](AppColumnDefBase.md)\<`TFeatures`, `TData`, `TValue`, `TCellComponents`, `THeaderComponents`\> + +#### Returns + +`TAccessor` *extends* `AccessorFn`\<`TData`\> ? `AccessorFnColumnDef`\<`TFeatures`, `TData`, `TValue`\> : `AccessorKeyColumnDef`\<`TFeatures`, `TData`, `TValue`\> + +*** + +### columns() + +```ts +columns: (columns) => ColumnDef[] & [...TColumns]; +``` + +Defined in: [createTableHook.tsx:203](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L203) + +Wraps an array of column definitions to preserve each column's individual TValue type. + +#### Type Parameters + +##### TColumns + +`TColumns` *extends* `ReadonlyArray`\<`ColumnDef`\<`TFeatures`, `TData`, `any`\>\> + +#### Parameters + +##### columns + +\[`...TColumns`\] + +#### Returns + +`ColumnDef`\<`TFeatures`, `TData`, `any`\>[] & \[`...TColumns`\] + +*** + +### display() + +```ts +display: (column) => DisplayColumnDef; +``` + +Defined in: [createTableHook.tsx:211](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L211) + +Creates a display column definition for non-data columns. +The cell, header, and footer contexts include pre-bound components. + +#### Parameters + +##### column + +[`AppDisplayColumnDef`](AppDisplayColumnDef.md)\<`TFeatures`, `TData`, `TCellComponents`, `THeaderComponents`\> + +#### Returns + +`DisplayColumnDef`\<`TFeatures`, `TData`, `unknown`\> + +*** + +### group() + +```ts +group: (column) => GroupColumnDef; +``` + +Defined in: [createTableHook.tsx:224](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L224) + +Creates a group column definition with nested child columns. +The cell, header, and footer contexts include pre-bound components. + +#### Parameters + +##### column + +[`AppGroupColumnDef`](AppGroupColumnDef.md)\<`TFeatures`, `TData`, `TCellComponents`, `THeaderComponents`\> + +#### Returns + +`GroupColumnDef`\<`TFeatures`, `TData`, `unknown`\> diff --git a/docs/framework/solid/reference/type-aliases/AppDisplayColumnDef.md b/docs/framework/solid/reference/type-aliases/AppDisplayColumnDef.md new file mode 100644 index 0000000000..262b9431d6 --- /dev/null +++ b/docs/framework/solid/reference/type-aliases/AppDisplayColumnDef.md @@ -0,0 +1,52 @@ +--- +id: AppDisplayColumnDef +title: AppDisplayColumnDef +--- + +# Type Alias: AppDisplayColumnDef\ + +```ts +type AppDisplayColumnDef = Omit, "cell" | "header" | "footer"> & object; +``` + +Defined in: [createTableHook.tsx:110](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L110) + +Enhanced display column definition with pre-bound components. + +## Type Declaration + +### cell? + +```ts +optional cell: AppColumnDefTemplate>; +``` + +### footer? + +```ts +optional footer: AppColumnDefTemplate>; +``` + +### header? + +```ts +optional header: AppColumnDefTemplate>; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> diff --git a/docs/framework/solid/reference/type-aliases/AppGroupColumnDef.md b/docs/framework/solid/reference/type-aliases/AppGroupColumnDef.md new file mode 100644 index 0000000000..c49ee362b2 --- /dev/null +++ b/docs/framework/solid/reference/type-aliases/AppGroupColumnDef.md @@ -0,0 +1,58 @@ +--- +id: AppGroupColumnDef +title: AppGroupColumnDef +--- + +# Type Alias: AppGroupColumnDef\ + +```ts +type AppGroupColumnDef = Omit, "cell" | "header" | "footer" | "columns"> & object; +``` + +Defined in: [createTableHook.tsx:133](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L133) + +Enhanced group column definition with pre-bound components. + +## Type Declaration + +### cell? + +```ts +optional cell: AppColumnDefTemplate>; +``` + +### columns? + +```ts +optional columns: ColumnDef[]; +``` + +### footer? + +```ts +optional footer: AppColumnDefTemplate>; +``` + +### header? + +```ts +optional header: AppColumnDefTemplate>; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> diff --git a/docs/framework/solid/reference/type-aliases/AppHeaderContext.md b/docs/framework/solid/reference/type-aliases/AppHeaderContext.md new file mode 100644 index 0000000000..63d80f7fb9 --- /dev/null +++ b/docs/framework/solid/reference/type-aliases/AppHeaderContext.md @@ -0,0 +1,75 @@ +--- +id: AppHeaderContext +title: AppHeaderContext +--- + +# Type Alias: AppHeaderContext\ + +```ts +type AppHeaderContext = object; +``` + +Defined in: [createTableHook.tsx:60](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L60) + +Enhanced HeaderContext with pre-bound header components. +The `header` property includes the registered headerComponents. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +## Properties + +### column + +```ts +column: Column; +``` + +Defined in: [createTableHook.tsx:66](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L66) + +*** + +### header + +```ts +header: Header & THeaderComponents & object; +``` + +Defined in: [createTableHook.tsx:67](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L67) + +#### Type Declaration + +##### FlexRender() + +```ts +FlexRender: () => JSXElement; +``` + +###### Returns + +`JSXElement` + +*** + +### table + +```ts +table: Table; +``` + +Defined in: [createTableHook.tsx:69](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L69) diff --git a/docs/framework/solid/reference/type-aliases/AppSolidTable.md b/docs/framework/solid/reference/type-aliases/AppSolidTable.md new file mode 100644 index 0000000000..8b4f002a11 --- /dev/null +++ b/docs/framework/solid/reference/type-aliases/AppSolidTable.md @@ -0,0 +1,144 @@ +--- +id: AppSolidTable +title: AppSolidTable +--- + +# Type Alias: AppSolidTable\ + +```ts +type AppSolidTable = SolidTable & NoInfer & object; +``` + +Defined in: [createTableHook.tsx:432](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L432) + +Extended table API returned by createAppTable with all App wrapper components + +## Type Declaration + +### AppCell + +```ts +AppCell: AppCellComponent>; +``` + +Wraps a cell and provides cell context with pre-bound cellComponents. +Optionally accepts a selector for Subscribe functionality. + +#### Example + +```tsx +// Without selector + + {(c) => } + + +// With selector - children receives cell and selected state + s.columnFilters}> + {(c, filters) => {filters.length}} + +``` + +### AppFooter + +```ts +AppFooter: AppHeaderComponent>; +``` + +Wraps a footer and provides header context with pre-bound headerComponents. +Optionally accepts a selector for Subscribe functionality. + +#### Example + +```tsx + + {(f) => } + +``` + +### AppHeader + +```ts +AppHeader: AppHeaderComponent>; +``` + +Wraps a header and provides header context with pre-bound headerComponents. +Optionally accepts a selector for Subscribe functionality. + +#### Example + +```tsx +// Without selector + + {(h) => } + + +// With selector + s.sorting}> + {(h, sorting) => {sorting.length} sorted} + +``` + +### AppTable + +```ts +AppTable: AppTableComponent; +``` + +Root wrapper component that provides table context with optional Subscribe. + +#### Example + +```tsx +// Without selector - children is JSXElement + + ...
+
+ +// With selector - children receives selected state + s.pagination}> + {(pagination) =>
Page {pagination.pageIndex}
} +
+``` + +### FlexRender + +```ts +FlexRender: typeof FlexRender; +``` + +Convenience FlexRender component attached to the table instance. +Renders cell, header, or footer content from column definitions. + +#### Example + +```tsx + + + +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TSelected + +`TSelected` + +### TTableComponents + +`TTableComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> diff --git a/docs/framework/solid/reference/type-aliases/ComponentType.md b/docs/framework/solid/reference/type-aliases/ComponentType.md new file mode 100644 index 0000000000..f1dc24b325 --- /dev/null +++ b/docs/framework/solid/reference/type-aliases/ComponentType.md @@ -0,0 +1,18 @@ +--- +id: ComponentType +title: ComponentType +--- + +# Type Alias: ComponentType\ + +```ts +type ComponentType = Component; +``` + +Defined in: [createTableHook.tsx:31](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L31) + +## Type Parameters + +### T + +`T` *extends* `Record`\<`string`, `any`\> diff --git a/docs/framework/solid/reference/type-aliases/CreateTableHookOptions.md b/docs/framework/solid/reference/type-aliases/CreateTableHookOptions.md new file mode 100644 index 0000000000..3e85c3355b --- /dev/null +++ b/docs/framework/solid/reference/type-aliases/CreateTableHookOptions.md @@ -0,0 +1,83 @@ +--- +id: CreateTableHookOptions +title: CreateTableHookOptions +--- + +# Type Alias: CreateTableHookOptions\ + +```ts +type CreateTableHookOptions = Omit, "columns" | "data" | "store" | "state" | "initialState"> & object; +``` + +Defined in: [createTableHook.tsx:242](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L242) + +Options for creating a table hook with pre-bound components and default table options. +Extends all TableOptions except 'columns' | 'data' | 'store' | 'state' | 'initialState'. + +## Type Declaration + +### cellComponents? + +```ts +optional cellComponents: TCellComponents; +``` + +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 + +```ts +{ TextCell, NumberCell, DateCell, CurrencyCell } +``` + +### headerComponents? + +```ts +optional headerComponents: THeaderComponents; +``` + +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 + +```ts +{ SortIndicator, ColumnFilter, ResizeHandle } +``` + +### tableComponents? + +```ts +optional tableComponents: TTableComponents; +``` + +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 + +```ts +{ PaginationControls, GlobalFilter, RowCount } +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TTableComponents + +`TTableComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> diff --git a/docs/framework/solid/reference/type-aliases/FlexRenderProps.md b/docs/framework/solid/reference/type-aliases/FlexRenderProps.md new file mode 100644 index 0000000000..ccab421116 --- /dev/null +++ b/docs/framework/solid/reference/type-aliases/FlexRenderProps.md @@ -0,0 +1,58 @@ +--- +id: FlexRenderProps +title: FlexRenderProps +--- + +# Type Alias: FlexRenderProps\ + +```ts +type FlexRenderProps = + | { + cell: Cell; + footer?: never; + header?: never; +} + | { + cell?: never; + footer?: never; + header: Header; +} + | { + cell?: never; + footer: Header; + header?: never; +}; +``` + +Defined in: [FlexRender.tsx:43](https://github.com/TanStack/table/blob/main/packages/solid-table/src/FlexRender.tsx#L43) + +Simplified component wrapper of `flexRender`. Use this utility component to render headers, cells, or footers with custom markup. +Only one prop (`cell`, `header`, or `footer`) may be passed. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` = `CellData` + +## Examples + +```ts + +``` + +```ts + +``` + +```ts + +``` diff --git a/docs/framework/solid/reference/type-aliases/SolidTable.md b/docs/framework/solid/reference/type-aliases/SolidTable.md new file mode 100644 index 0000000000..4465dc6993 --- /dev/null +++ b/docs/framework/solid/reference/type-aliases/SolidTable.md @@ -0,0 +1,173 @@ +--- +id: SolidTable +title: SolidTable +--- + +# Type Alias: SolidTable\ + +```ts +type SolidTable = Table & object; +``` + +Defined in: [createTable.ts:27](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTable.ts#L27) + +## Type Declaration + +### FlexRender + +```ts +FlexRender: typeof FlexRender; +``` + +Convenience FlexRender component attached to the table instance for +rendering headers, cells, or footers with custom markup. Mirrors the +`table.FlexRender` API exposed by `createTableHook`'s `createAppTable`. + +#### Example + +```ts + + + +``` + +### state + +```ts +readonly state: Accessor>; +``` + +The selected state of the table. This state may not match the structure of `table.store.state` because it is selected by the `selector` function that you pass as the 2nd argument to `createTable`. + +#### Example + +```ts +const table = createTable(options, (state) => ({ globalFilter: state.globalFilter })) // only globalFilter is part of the selected state + +console.log(table.state().globalFilter) +``` + +### Subscribe() + +```ts +Subscribe: { + (props): Element; + (props): Element; + (props): Element; +}; +``` + +Subscribe to the store (selector required) or a single source (atom or store). +Source **without** `selector` is a separate overload so children receive +`Accessor` (identity projection). Source overloads are listed first +for JSX contextual typing. + +#### Call Signature + +```ts +(props): Element; +``` + +##### Type Parameters + +###### TSourceValue + +`TSourceValue` + +##### Parameters + +###### props + +###### children + +(`state`) => `JSX.Element` \| `JSX.Element` + +###### selector? + +`undefined` + +###### source + +[`SubscribeSource`](SubscribeSource.md)\<`TSourceValue`\> + +##### Returns + +`Element` + +#### Call Signature + +```ts +(props): Element; +``` + +##### Type Parameters + +###### TSourceValue + +`TSourceValue` + +###### TSubSelected + +`TSubSelected` + +##### Parameters + +###### props + +###### children + +(`state`) => `JSX.Element` \| `JSX.Element` + +###### selector + +(`state`) => `TSubSelected` + +###### source + +[`SubscribeSource`](SubscribeSource.md)\<`TSourceValue`\> + +##### Returns + +`Element` + +#### Call Signature + +```ts +(props): Element; +``` + +##### Type Parameters + +###### TSubSelected + +`TSubSelected` + +##### Parameters + +###### props + +###### children + +(`state`) => `JSX.Element` \| `JSX.Element` + +###### selector + +(`state`) => `TSubSelected` + +##### Returns + +`Element` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TSelected + +`TSelected` = `TableState`\<`TFeatures`\> diff --git a/docs/framework/solid/reference/type-aliases/SubscribeSource.md b/docs/framework/solid/reference/type-aliases/SubscribeSource.md new file mode 100644 index 0000000000..78280a3c73 --- /dev/null +++ b/docs/framework/solid/reference/type-aliases/SubscribeSource.md @@ -0,0 +1,22 @@ +--- +id: SubscribeSource +title: SubscribeSource +--- + +# Type Alias: SubscribeSource\ + +```ts +type SubscribeSource = + | Atom + | ReadonlyAtom + | Store +| ReadonlyStore; +``` + +Defined in: [createTable.ts:21](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTable.ts#L21) + +## Type Parameters + +### TValue + +`TValue` diff --git a/docs/framework/solid/solid-table.md b/docs/framework/solid/solid-table.md index dc5afc392e..561ab79188 100644 --- a/docs/framework/solid/solid-table.md +++ b/docs/framework/solid/solid-table.md @@ -2,18 +2,160 @@ title: Solid Table --- -The `@tanstack/solid-table` adapter is a wrapper around the core table logic. Most of it's job is related to managing state the "solid" way, providing types and the rendering implementation of cell/header/footer templates. +The `@tanstack/solid-table` adapter wraps `@tanstack/table-core` with Solid-specific reactivity, rendering helpers, and types. It installs the Solid `coreReativityFeature` for you, so table atoms participate in Solid dependency tracking and can update computations with fine-grained precision. -## `createSolidTable` +TanStack Table v9 is explicit about what a table uses. Register features with `_features`, and register client-side row model factories with `_rowModels`. The core row model is included by default, so a basic table can use `_rowModels: {}`. -Takes an `options` object and returns a table. +## Creating a Table + +Use `createTable` to create a Solid table instance. + +```tsx +import { createTable, tableFeatures, type ColumnDef } from '@tanstack/solid-table' + +type Person = { + firstName: string + lastName: string + age: number +} + +const _features = tableFeatures({}) + +const columns: Array> = [ + { + accessorKey: 'firstName', + header: 'First name', + cell: (info) => info.getValue(), + }, +] + +function App(props: { data: Person[] }) { + const table = createTable({ + _features, + _rowModels: {}, + columns, + get data() { + return props.data + }, + }) + + return null +} +``` + +For feature-specific row models, register the feature and put the row model factory under `_rowModels`. + +```tsx +import { + createPaginatedRowModel, + createSortedRowModel, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/solid-table' + +const _features = tableFeatures({ + rowPaginationFeature, + rowSortingFeature, +}) + +const tableOptions = { + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, +} +``` + +## Table State + +Table state is managed with TanStack Store atoms in v9. For most tables, you do not need to manage table state yourself: set `initialState` when you need starting values, and use feature APIs like `table.nextPage()`, `table.setSorting(...)`, and `row.toggleSelected()` instead of mutating state directly. + +Use `atoms` when your app should own one state slice with TanStack Store. Use `state` with the matching `on[State]Change` option for simple Solid state integration or migration paths. Selected table state is available through the `table.state()` accessor when you pass a selector to `createTable`. + +```tsx +import { createAtom } from '@tanstack/solid-store' +import { + createTable, + rowPaginationFeature, + tableFeatures, + type PaginationState, +} from '@tanstack/solid-table' + +const _features = tableFeatures({ + rowPaginationFeature, +}) + +const paginationAtom = createAtom({ + pageIndex: 0, + pageSize: 10, +}) + +const table = createTable({ + _features, + _rowModels: {}, + columns, + data, + atoms: { + pagination: paginationAtom, + }, +}) +``` + +See the [Table State Guide](./guide/table-state.md) for selectors, external atoms, and state ownership patterns. + +## Rendering Headers, Cells, and Footers + +Use `table.FlexRender` to render column `header`, `cell`, and `footer` definitions. It handles plain values and Solid components. ```tsx -import { createSolidTable } from '@tanstack/solid-table' + + + {(row) => ( + + + {(cell) => ( + + + + )} + + + )} + + +``` + +## createTableHook + +`createTableHook` creates an app-specific table creator. Use it when multiple tables should share `_features`, `_rowModels`, default options, column helpers, and component conventions. + +```tsx +import { createTableHook, tableFeatures } from '@tanstack/solid-table' -function App() { - const table = createSolidTable(options) +const { createAppTable, createAppColumnHelper } = createTableHook({ + _features: tableFeatures({}), + _rowModels: {}, +}) - // ...render your table +const columnHelper = createAppColumnHelper() + +function App(props: { data: Person[] }) { + const table = createAppTable({ + columns, + get data() { + return props.data + }, + }) + + return null } ``` + +See the [Composable Tables example](./examples/composable-tables) for the full pattern. + +## API Reference + +See the [Solid API Reference](./reference/index.md). diff --git a/docs/framework/svelte/guide/table-state.md b/docs/framework/svelte/guide/table-state.md index db8ff045ea..d451400f63 100644 --- a/docs/framework/svelte/guide/table-state.md +++ b/docs/framework/svelte/guide/table-state.md @@ -2,259 +2,337 @@ title: Table State (Svelte) Guide --- +## Examples + +Want to skip to the implementation? Check out these examples: + +- [Basic createTable](../examples/basic-create-table) +- [Basic External Atoms](../examples/basic-external-atoms) +- [Basic External State](../examples/basic-external-state) +- [With TanStack Query](../examples/with-tanstack-query) + ## Table State (Svelte) Guide -TanStack Table has a simple underlying internal state management system to store and manage the state of the table. It also lets you selectively pull out any state that you need to manage in your own state management. This guide will walk you through the different ways in which you can interact with and manage the state of the table. +> **If you boil TanStack Table down to one sentence: TanStack Table is a large state-management coordinator for table states.** -### Accessing Table State +Understanding this guide is fundamental to understanding how TanStack Table works and how to interact with it for the best results. -You do not need to set up anything special in order for the table state to work. If you pass nothing into either `state`, `initialState`, or any of the `on[State]Change` table options, the table will manage its own state internally. You can access any part of this internal state by using the `table.getState()` table instance API. +### Do you need to Manage External State? -```jsx -const options = writable({ - columns, - data, - //... -}) +You usually do NOT need to manage table state yourself. If you pass nothing to `initialState`, `atoms`, `state`, or any of the `on[State]Change` table options, TanStack Table will manage its own state internally. -const table = createSvelteTable(options) +There will be situations where you need to customize how you interact with the internal table state, or even hoist it up to your own scopes. TanStack Table lets you read, subscribe to, or own the state slices that matter to your app. This guide explains how table state works in Svelte, how to read it, and when to use external atoms or external state. -console.log(table.getState()) //access the entire internal state -console.log(table.getState().rowSelection) //access just the row selection state -``` +### State in v9 -### Custom Initial State +TanStack Table v9 overhauled state management around TanStack Store. TanStack Store uses the `alien-signals` implementation and supports performant derived state. For Svelte 5, the table adapter supplies custom reactivity so table state atoms are backed by runes. -If all you need to do for certain states is customize their initial default values, you still do not need to manage any of the state yourself. You can simply set values in the `initialState` option of the table instance. +A table instance has a few state surfaces: + +- `table.baseAtoms` are the internal writable atoms created from the resolved initial state. +- `table.atoms` are readonly derived atoms exposed per registered state slice. +- `table.store` is a readonly flat TanStack Store derived by putting all of the registered `table.atoms` together. +- `table.state` is Svelte-only selected state. It is the value returned from the selector passed as the second argument to `createTable`. + +The Svelte adapter provides `svelteReactivity()` to the table's `coreReativityFeature`. Core readonly atoms are backed by `$derived.by`, writable atoms are backed by `$state`, and `createTable` uses `$effect.pre` to sync options and controlled state before DOM updates. Table APIs that read atoms participate in Svelte dependency tracking through those rune-backed atom reads. + +### Feature-based State + +State slices are only created for the features that are registered in `_features`. This keeps TanStack Table tree-shakeable and gives TypeScript more accurate state inference. + +```ts +const _features = tableFeatures({ + rowPaginationFeature, + rowSortingFeature, +}) -```jsx -const options = writable({ +const table = createTable({ + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, columns, - data, - initialState: { - columnOrder: ['age', 'firstName', 'lastName'], //customize the initial column order - columnVisibility: { - id: false //hide the id column by default - }, - expanded: true, //expand all rows by default - sorting: [ - { - id: 'age', - desc: true //sort by age in descending order by default - } - ] + get data() { + return data }, - //... }) -const table = createSvelteTable(options) +table.atoms.pagination.get() +table.atoms.sorting.get() + +// table.atoms.rowSelection // TypeScript error unless rowSelectionFeature is registered ``` -> **Note**: Only specify each particular state in either `initialState` or `state`, but not both. If you pass in a particular state value to both `initialState` and `state`, the initialized state in `state` will take overwrite any corresponding value in `initialState`. +If `_features` does not include a feature, its state should not be available in `table.atoms`, `table.store.state`, `table.state`, `initialState`, `state`, or `atoms`. -### Controlled State +### Accessing Table State + +There are two different questions when reading table state: + +- Do you only need the current value? +- Or should a Svelte reactive update happen when that value changes? + +Use a direct atom or store read for the current value. Use selected state, `useSelector`, or `subscribeTable` when you want fine-grained reactive updates. + +#### Reading State Without Subscribing + +The simplest and most performant way to read a current state value is to read the matching atom: + +```ts +const pagination = table.atoms.pagination.get() +const sorting = table.atoms.sorting.get() +``` -If you need easy access to the table state in other areas of your application, TanStack Table makes it easy to control and manage any or all of the table state in your own state management system. You can do this by passing in your own state and state management functions to the `state` and `on[State]Change` table options. +You can also read the current flat store snapshot: -#### Individual Controlled State +```ts +const tableState = table.store.state +const pagination = table.store.state.pagination +``` -You can control just the state that you need easy access to. You do NOT have to control all of the table state if you do not need to. It is recommended to only control the state that you need on a case-by-case basis. +These reads are current-value reads. They only participate in Svelte dependency tracking when they are called in a rune-tracked context. If the UI needs to stay reactive to table state changes, use `table.state`, `subscribeTable`, or even a `useSelector` hook from TanStack Store. -In order to control a particular state, you need to both pass in the corresponding `state` value and the `on[State]Change` function to the table instance. +#### Reading Reactive State with createTable -Let's take filtering, sorting, and pagination as an example in a "manual" server-side data fetching scenario. You can store the filtering, sorting, and pagination state in your own state management, but leave out any other state like column order, column visibility, etc. if your API does not care about those values. +The second argument to `createTable` is a TanStack Store selector. The selected value is exposed as `table.state`. The default selector selects all registered table state. ```ts -let sorting = [ +const table = createTable( { - id: 'age', - desc: true, //sort by age in descending order by default - }, -] -const setSorting = updater => { - if (updater instanceof Function) { - sorting = updater(sorting) - } else { - sorting = updater - } - options.update(old => ({ - ...old, - state: { - ...old.state, - sorting, + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), }, - })) -} - -let columnFilters = [] //no default filters -const setColumnFilters = updater => { - if (updater instanceof Function) { - columnFilters = updater(columnFilters) - } else { - columnFilters = updater - } - options.update(old => ({ - ...old, - state: { - ...old.state, - columnFilters, + columns, + get data() { + return data }, - })) -} + }, + (state) => ({ + pagination: state.pagination, + }), +) -let pagination = { pageIndex: 0, pageSize: 15 } //default pagination -const setPagination = updater => { - if (updater instanceof Function) { - pagination = updater(pagination) - } else { - pagination = updater - } - options.update(old => ({ - ...old, - state: { - ...old.state, - pagination, - }, - })) -} +table.state.pagination +``` -//Use our controlled state values to fetch data -const tableQuery = createQuery({ - queryKey: ['users', columnFilters, sorting, pagination], - queryFn: () => fetchUsers(columnFilters, sorting, pagination), - //... -}) +#### Fine-grained Updates with subscribeTable + +Use `subscribeTable` when you want a specific part of Svelte code to subscribe to one atom or store source. It uses TanStack Store selection and exposes the selected value through `.current`. + +```ts +import { subscribeTable } from '@tanstack/svelte-table' + +const pagination = subscribeTable(table.atoms.pagination) +const pageIndex = subscribeTable( + table.atoms.pagination, + (pagination) => pagination.pageIndex, +) +``` + +```svelte + + Page {pagination.current.pageIndex + 1} of {table.getPageCount()} + +``` + +### Setting Table State + +You should almost never need to set table state directly. TanStack Table features expose dedicated APIs for interacting with their state, and those APIs are the safest way to make changes. + +```ts +table.nextPage() +table.previousPage() +table.setPageIndex(0) +table.setPageSize(25) +``` + +Use APIs like `table.setSorting(...)`, `table.setColumnFilters(...)`, `column.toggleVisibility()`, or `row.toggleSelected()` instead of manually editing the underlying state object. + +If you only care about setting starting values, use `initialState`. If you want to reset a state slice back to its initial value, use that feature's reset API. + +If you really do need to write a state slice directly, the low-level write surface for internally owned state is the matching base atom: + +```ts +table.baseAtoms.pagination.set((old) => ({ + ...old, + pageIndex: 0, +})) +``` + +Direct base atom writes should be rare. If a slice is owned by an external atom passed through `atoms`, write to that external atom instead; `table.atoms.pagination` will read from the external atom, not the internal base atom. + +### Custom Initial State + +If you only need to customize the starting value for some table state, use `initialState`. You still do not need to manage that state yourself. + +`initialState` only applies to registered state slices. It is used to create the table's initial state and is also used by reset APIs such as `table.resetSorting()` or `table.resetPagination()`. Changing the `initialState` object later does not reset table state. -const options = writable({ +```ts +const table = createTable({ + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, columns, - data: tableQuery.data, - //... - state: { - columnFilters, //pass controlled state back to the table (overrides internal state) - sorting, - pagination + get data() { + return data + }, + initialState: { + sorting: [ + { + id: 'age', + desc: true, + }, + ], + pagination: { + pageIndex: 0, + pageSize: 25, + }, }, - onColumnFiltersChange: setColumnFilters, //hoist columnFilters state into our own state management - onSortingChange: setSorting, - onPaginationChange: setPagination, }) +``` + +> **Note:** Do not provide the same state slice in multiple ownership places unless you intentionally want one to win. For a slice like `pagination`, prefer exactly one of `initialState.pagination`, `atoms.pagination`, or `state.pagination` as the source of truth. External atoms take precedence over external `state`; external `state` syncs into the table's internal base atom. -const table = createSvelteTable(options) -//... +#### Resetting to Initial State + +Feature reset APIs reset to `table.initialState` by default. Many reset APIs also accept `true` to reset to that feature's blank/default state instead: + +```ts +table.resetSorting() +table.resetPagination() +table.resetPagination(true) ``` -#### Fully Controlled State +Slice reset APIs like `resetPagination()` update through that feature's state updater and can update an externally owned atom. The core `table.reset()` API resets the internal base atoms, so do not use it as the primary way to reset state that is owned by external atoms. + +### Controlled State + +If you need easy access to table state in other parts of your application, you can control individual state slices. In v9, external atoms are the recommended way to do this when you want atomic ownership and fine-grained Svelte updates. + +#### External Atoms + +Use external atoms when the app should own one or more table state slices. Create stable writable atoms with `createAtom`, pass them to `atoms`, and subscribe to them with `useSelector` or `subscribeTable`. + +```ts +import { createAtom, useSelector } from '@tanstack/svelte-store' +import { + createTable, + rowPaginationFeature, + tableFeatures, + type PaginationState, +} from '@tanstack/svelte-table' + +const _features = tableFeatures({ + rowPaginationFeature, +}) -Alternatively, you can control the entire table state with the `onStateChange` table option. It will hoist out the entire table state into your own state management system. Be careful with this approach, as you might find that raising some frequently changing state values up a svelte tree, like `columnSizingInfo` state`, might cause bad performance issues. +const paginationAtom = createAtom({ + pageIndex: 0, + pageSize: 10, +}) -A couple of more tricks may be needed to make this work. If you use the `onStateChange` table option, the initial values of the `state` must be populated with all of the relevant state values for all of the features that you want to use. You can either manually type out all of the initial state values, or use the `table.setOptions` API in a special way as shown below. +const pagination = useSelector(paginationAtom) -```jsx -//create a table instance with default state values -const options = writable({ +const table = createTable({ + _features, + _rowModels: {}, columns, - data, - //... Note: `state` values are NOT passed in yet + get data() { + return dataQuery.data?.rows ?? [] + }, + get rowCount() { + return dataQuery.data?.rowCount + }, + atoms: { + pagination: paginationAtom, + }, + manualPagination: true, }) -const table = createSvelteTable(options) - -let state = { - ...table.initialState, //populate the initial state with all of the default state values from the table instance - pagination: { - pageIndex: 0, - pageSize: 15 //optionally customize the initial pagination state. - } -} -const setState = updater => { - if (updater instanceof Function) { - state = updater(state) - } else { - state = updater - } - options.update(old => ({ - ...old, - state, - })) -} -//Use the table.setOptions API to merge our fully controlled state onto the table instance -table.setOptions(prev => ({ - ...prev, //preserve any other options that we have set up above - state, //our fully controlled state overrides the internal state - onStateChange: setState //any state changes will be pushed up to our own state management -})) +// pagination.current is reactive, and table pagination APIs update paginationAtom ``` -### On State Change Callbacks - -So far, we have seen the `on[State]Change` and `onStateChange` table options work to "hoist" the table state changes into our own state management. However, there are a few things about these using these options that you should be aware of. +When using the `atoms` option for a slice, you do not need to add the matching `on[State]Change` option. -#### 1. **State Change Callbacks MUST have their corresponding state value in the `state` option**. +#### External State -Specifying an `on[State]Change` callback tells the table instance that this will be a controlled state. If you do not specify the corresponding `state` value, that state will be "frozen" with its initial value. +The classic `state` plus `on[State]Change` pattern is still supported. This can be convenient for simple integrations or when migrating v8 code, but it is less atomic than external atoms. ```ts -let sorting = [] -const setSorting = updater => { - if (updater instanceof Function) { - sorting = updater(sorting) - } else { - sorting = updater - } - options.update(old => ({ - ...old, - state: { - ...old.state, - sorting, - }, - })) -} -//... -const options = writable({ +let sorting: SortingState = $state([]) +let pagination: PaginationState = $state({ + pageIndex: 0, + pageSize: 10, +}) + +const table = createTable({ + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, columns, - data, - //... + get data() { + return data + }, state: { - sorting, //required because we are using `onSortingChange` + get sorting() { + return sorting + }, + get pagination() { + return pagination + }, + }, + onSortingChange: (updater) => { + sorting = updater instanceof Function ? updater(sorting) : updater + }, + onPaginationChange: (updater) => { + pagination = updater instanceof Function ? updater(pagination) : updater }, - onSortingChange: setSorting, //makes the `state.sorting` controlled }) -const table = createSvelteTable(options) ``` -#### 2. **Updaters can either be raw values or callback functions**. +The v8-style `onStateChange` option is no longer part of the v9 `createTable` state model. v9 encourages keeping table state slices atomic and separated for performance. + +##### On State Change Callbacks -The `on[State]Change` and `onStateChange` callbacks work exactly like the `setState` functions in React. The updater values can either be a new state value or a callback function that takes the previous state value and returns the new state value. +The `on[State]Change` callbacks are useful when you are controlling a matching slice through the `state` option. They receive either a raw value or an updater function. -What implications does this have? It means that if you want to add in some extra logic in any of the `on[State]Change` callbacks, you can do so, but you need to check whether or not the new incoming updater value is a function or value. +If you provide an `on[State]Change` callback, also provide the corresponding value in `state`. For example, `onSortingChange` should be paired with `state.sorting`. -This is why you see the `if (updater instanceof Function)` check in the `setState` functions in the examples above. +```ts +onPaginationChange: (updater) => { + pagination = updater instanceof Function ? updater(pagination) : updater +} +``` ### State Types -All complex states in TanStack Table have their own TypeScript types that you can import and use. This can be handy for ensuring that you are using the correct data structures and properties for the state values that you are controlling. +Most complex states in TanStack Table have their own TypeScript types that you can import and use. ```ts -import { createSvelteTable, type SortingState, type Updater } from '@tanstack/svelte-table' -//... -let sorting: SortingState[] = [ +import { + createTable, + type PaginationState, + type RowSelectionState, + type SortingState, + type TableState, +} from '@tanstack/svelte-table' + +let sorting: SortingState = $state([ { - id: 'age', //you should get autocomplete for the `id` and `desc` properties + id: 'age', desc: true, - } -] -const setSorting = (updater: Updater) => { - if (updater instanceof Function) { - sorting = updater(sorting) - } else { - sorting = updater - } - options.update(old => ({ - ...old, - state: { - ...old.state, - sorting, - }, - })) -} + }, +]) +``` + +`TableState` is inferred from the features registered on that table: + +```ts +type MyTableState = TableState ``` diff --git a/docs/framework/svelte/reference/functions/createTable.md b/docs/framework/svelte/reference/functions/createTable.md new file mode 100644 index 0000000000..f608bd0969 --- /dev/null +++ b/docs/framework/svelte/reference/functions/createTable.md @@ -0,0 +1,65 @@ +--- +id: createTable +title: createTable +--- + +# Function: createTable() + +```ts +function createTable(tableOptions, selector?): SvelteTable; +``` + +Defined in: [packages/svelte-table/src/createTable.svelte.ts:55](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/createTable.svelte.ts#L55) + +Creates a Svelte 5 table instance backed by rune-aware TanStack Store atoms. + +The optional selector projects from `table.store`; the selected value is +exposed on `table.state`. The adapter syncs options in `$effect.pre`, so +reactive option getters and external `$state` values are applied before DOM +updates read table APIs such as `getRowModel()`. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TSelected + +`TSelected` = `TableState`\<`TFeatures`\> + +## Parameters + +### tableOptions + +`TableOptions`\<`TFeatures`, `TData`\> + +### selector? + +(`state`) => `TSelected` + +## Returns + +[`SvelteTable`](../type-aliases/SvelteTable.md)\<`TFeatures`, `TData`, `TSelected`\> + +## Example + +```svelte + + +{table.state.pagination.pageIndex} +``` diff --git a/docs/framework/svelte/reference/functions/createTableHook.md b/docs/framework/svelte/reference/functions/createTableHook.md new file mode 100644 index 0000000000..f90ac163ac --- /dev/null +++ b/docs/framework/svelte/reference/functions/createTableHook.md @@ -0,0 +1,197 @@ +--- +id: createTableHook +title: createTableHook +--- + +# Function: createTableHook() + +```ts +function createTableHook(__namedParameters): object; +``` + +Defined in: [packages/svelte-table/src/createTableHook.svelte.ts:419](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/createTableHook.svelte.ts#L419) + +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 + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TTableComponents + +`TTableComponents` *extends* `Record`\<`string`, [`ComponentType`](../type-aliases/ComponentType.md)\<`any`\>\> + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](../type-aliases/ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](../type-aliases/ComponentType.md)\<`any`\>\> + +## Parameters + +### \_\_namedParameters + +[`CreateTableHookOptions`](../type-aliases/CreateTableHookOptions.md)\<`TFeatures`, `TTableComponents`, `TCellComponents`, `THeaderComponents`\> + +## Returns + +### appFeatures + +```ts +appFeatures: TFeatures; +``` + +### createAppColumnHelper() + +```ts +createAppColumnHelper: () => AppColumnHelper; +``` + +Create a column helper pre-bound to the features and components configured in this table hook. +The cell, header, and footer contexts include pre-bound components (e.g., `cell.TextCell`). + +#### Type Parameters + +##### TData + +`TData` *extends* `RowData` + +#### Returns + +[`AppColumnHelper`](../type-aliases/AppColumnHelper.md)\<`TFeatures`, `TData`, `TCellComponents`, `THeaderComponents`\> + +### createAppTable() + +```ts +createAppTable: (tableOptions, selector?) => AppSvelteTable; +``` + +Enhanced createTable hook that returns a table with App wrapper components +and pre-bound tableComponents attached directly to the table object. + +Default options from createTableHook are automatically merged with +the options passed here. Options passed here take precedence. + +TFeatures is already known from the createTableHook call; TData is inferred from the data prop. + +#### Type Parameters + +##### TData + +`TData` *extends* `RowData` + +##### TSelected + +`TSelected` = `TableState`\<`TFeatures`\> + +#### Parameters + +##### tableOptions + +`Omit`\<`TableOptions`\<`TFeatures`, `TData`\>, `"_features"` \| `"_rowModels"`\> + +##### selector? + +(`state`) => `TSelected` + +#### Returns + +[`AppSvelteTable`](../type-aliases/AppSvelteTable.md)\<`TFeatures`, `TData`, `TSelected`, `TTableComponents`, `TCellComponents`, `THeaderComponents`\> + +### useCellContext() + +```ts +useCellContext: () => Cell; +``` + +Access the cell instance from within an `AppCell` wrapper. +Use this in custom `cellComponents` passed to `createTableHook`. +TFeatures is already known from the createTableHook call. + +#### Type Parameters + +##### TValue + +`TValue` *extends* `unknown` = `unknown` + +#### Returns + +`Cell`\<`TFeatures`, `any`, `TValue`\> + +### useHeaderContext() + +```ts +useHeaderContext: () => Header; +``` + +Access the header instance from within an `AppHeader` or `AppFooter` wrapper. +Use this in custom `headerComponents` passed to `createTableHook`. +TFeatures is already known from the createTableHook call. + +#### Type Parameters + +##### TValue + +`TValue` *extends* `unknown` = `unknown` + +#### Returns + +`Header`\<`TFeatures`, `any`, `TValue`\> + +### useTableContext() + +```ts +useTableContext: () => SvelteTable; +``` + +Access the table instance from within an `AppTable` wrapper. +Use this in custom `tableComponents` passed to `createTableHook`. +TFeatures is already known from the createTableHook call. + +#### Type Parameters + +##### TData + +`TData` *extends* `RowData` = `RowData` + +#### Returns + +[`SvelteTable`](../type-aliases/SvelteTable.md)\<`TFeatures`, `TData`\> + +## Example + +```ts +// hooks/table.ts +export const { + createAppTable, + createAppColumnHelper, + useTableContext, + useCellContext, + useHeaderContext, +} = createTableHook({ + _features: tableFeatures({ + rowPaginationFeature, + rowSortingFeature, + columnFilteringFeature, + }), + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + filteredRowModel: createFilteredRowModel(filterFns), + }, + tableComponents: { PaginationControls, RowCount }, + cellComponents: { TextCell, NumberCell }, + headerComponents: { SortIndicator, ColumnFilter }, +}) +``` diff --git a/docs/framework/svelte/reference/functions/createTableState.md b/docs/framework/svelte/reference/functions/createTableState.md new file mode 100644 index 0000000000..d4c22f79ff --- /dev/null +++ b/docs/framework/svelte/reference/functions/createTableState.md @@ -0,0 +1,43 @@ +--- +id: createTableState +title: createTableState +--- + +# Function: createTableState() + +```ts +function createTableState(initialValue): [() => TState, (updater) => void]; +``` + +Defined in: [packages/svelte-table/src/createTableState.svelte.ts:18](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/createTableState.svelte.ts#L18) + +Creates a small Svelte 5 state holder that accepts TanStack Table updaters. + +This is useful when a table state slice should be owned outside the table +with `$state`, but still needs to accept both value and functional updater +forms from `on[State]Change` callbacks. + +## Type Parameters + +### TState + +`TState` + +## Parameters + +### initialValue + +`TState` + +## Returns + +\[() => `TState`, (`updater`) => `void`\] + +## Example + +```ts +const [pagination, setPagination] = createTableState({ + pageIndex: 0, + pageSize: 10, +}) +``` diff --git a/docs/framework/svelte/reference/functions/renderComponent.md b/docs/framework/svelte/reference/functions/renderComponent.md new file mode 100644 index 0000000000..6a83088152 --- /dev/null +++ b/docs/framework/svelte/reference/functions/renderComponent.md @@ -0,0 +1,66 @@ +--- +id: renderComponent +title: renderComponent +--- + +# Function: renderComponent() + +```ts +function renderComponent(component, props?): RenderComponentConfig; +``` + +Defined in: [packages/svelte-table/src/render-component.ts:71](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/render-component.ts#L71) + +Wraps a Svelte component so it can be returned from a column definition +renderer such as `cell`, `header`, or `footer`. + +This is only to be used with Svelte Components - use `renderSnippet` for Svelte Snippets. + +## Type Parameters + +### TComponent + +`TComponent` *extends* `Component`\<`any`, \{ +\}, `string`\> + +### TProps + +`TProps` *extends* `any` + +## Parameters + +### component + +`TComponent` + +A Svelte component + +### props? + +`TProps` + +The props to pass to `component` + +## Returns + +`RenderComponentConfig`\<`TComponent`\> + +A `RenderComponentConfig` object that helps svelte-table know how to render the header/cell component. + +## Example + +```ts +// +page.svelte +const defaultColumns = [ + columnHelper.accessor('name', { + header: header => renderComponent(SortHeader, { label: 'Name', header }), + }), + columnHelper.accessor('state', { + header: header => renderComponent(SortHeader, { label: 'State', header }), + }), +] +``` + +## See + +[https://tanstack.com/table/latest/docs/guide/column-defs](https://tanstack.com/table/latest/docs/guide/column-defs) diff --git a/docs/framework/svelte/reference/functions/renderSnippet.md b/docs/framework/svelte/reference/functions/renderSnippet.md new file mode 100644 index 0000000000..030a61b0a2 --- /dev/null +++ b/docs/framework/svelte/reference/functions/renderSnippet.md @@ -0,0 +1,63 @@ +--- +id: renderSnippet +title: renderSnippet +--- + +# Function: renderSnippet() + +```ts +function renderSnippet(snippet, params?): RenderSnippetConfig; +``` + +Defined in: [packages/svelte-table/src/render-component.ts:104](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/render-component.ts#L104) + +Wraps a Svelte snippet so it can be returned from a column definition +renderer such as `cell`, `header`, or `footer`. + +*The snippet must only take one parameter.* + +This is only to be used with Snippets - use `renderComponent` for Svelte Components. + +## Type Parameters + +### TProps + +`TProps` + +## Parameters + +### snippet + +`Snippet`\<\[`TProps`\]\> + +The snippet to render. + +### params? + +`TProps` + +The single parameter object passed to the snippet. + +## Returns + +`RenderSnippetConfig`\<`TProps`\> + +A `RenderSnippetConfig` consumed by the Svelte `FlexRender` component. + +## Example + +```ts +// +page.svelte +const defaultColumns = [ + columnHelper.accessor('name', { + cell: cell => renderSnippet(nameSnippet, { name: cell.row.name }), + }), + columnHelper.accessor('state', { + cell: cell => renderSnippet(stateSnippet, { state: cell.row.state }), + }), +] +``` + +## See + +[https://tanstack.com/table/latest/docs/guide/column-defs](https://tanstack.com/table/latest/docs/guide/column-defs) diff --git a/docs/framework/svelte/reference/functions/subscribeTable.md b/docs/framework/svelte/reference/functions/subscribeTable.md new file mode 100644 index 0000000000..d13d94c23f --- /dev/null +++ b/docs/framework/svelte/reference/functions/subscribeTable.md @@ -0,0 +1,112 @@ +--- +id: subscribeTable +title: subscribeTable +--- + +# Function: subscribeTable() + +## Call Signature + +```ts +function subscribeTable(source): object; +``` + +Defined in: [packages/svelte-table/src/subscribe.ts:34](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/subscribe.ts#L34) + +Creates a fine-grained Svelte subscription to a TanStack Store source. + +Pass a table atom or store and optionally project it with a selector. The +returned selector store exposes `.current`, making it useful for reading +focused table state outside the broad `createTable` selector. + +### Type Parameters + +#### TSourceValue + +`TSourceValue` + +### Parameters + +#### source + +[`SubscribeSource`](../type-aliases/SubscribeSource.md)\<`TSourceValue`\> + +### Returns + +`object` + +#### current + +```ts +readonly current: NoInfer; +``` + +### Example + +```svelte + + + +``` + +## Call Signature + +```ts +function subscribeTable(source, selector): object; +``` + +Defined in: [packages/svelte-table/src/subscribe.ts:37](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/subscribe.ts#L37) + +Creates a fine-grained Svelte subscription to a TanStack Store source. + +Pass a table atom or store and optionally project it with a selector. The +returned selector store exposes `.current`, making it useful for reading +focused table state outside the broad `createTable` selector. + +### Type Parameters + +#### TSourceValue + +`TSourceValue` + +#### TSelected + +`TSelected` + +### Parameters + +#### source + +[`SubscribeSource`](../type-aliases/SubscribeSource.md)\<`TSourceValue`\> + +#### selector + +(`state`) => `TSelected` + +### Returns + +`object` + +#### current + +```ts +readonly current: TSelected; +``` + +### Example + +```svelte + + + +``` diff --git a/docs/framework/svelte/reference/index.md b/docs/framework/svelte/reference/index.md new file mode 100644 index 0000000000..ff1e69c352 --- /dev/null +++ b/docs/framework/svelte/reference/index.md @@ -0,0 +1,35 @@ +--- +id: "@tanstack/svelte-table" +title: "@tanstack/svelte-table" +--- + +# @tanstack/svelte-table + +## Type Aliases + +- [AppCellContext](type-aliases/AppCellContext.md) +- [AppColumnDefBase](type-aliases/AppColumnDefBase.md) +- [AppColumnDefTemplate](type-aliases/AppColumnDefTemplate.md) +- [AppColumnHelper](type-aliases/AppColumnHelper.md) +- [AppDisplayColumnDef](type-aliases/AppDisplayColumnDef.md) +- [AppGroupColumnDef](type-aliases/AppGroupColumnDef.md) +- [AppHeaderContext](type-aliases/AppHeaderContext.md) +- [AppSvelteTable](type-aliases/AppSvelteTable.md) +- [ComponentType](type-aliases/ComponentType.md) +- [CreateTableHookOptions](type-aliases/CreateTableHookOptions.md) +- [FlexRender](type-aliases/FlexRender.md) +- [SubscribeSource](type-aliases/SubscribeSource.md) +- [SvelteTable](type-aliases/SvelteTable.md) + +## Variables + +- [FlexRender](variables/FlexRender.md) + +## Functions + +- [createTable](functions/createTable.md) +- [createTableHook](functions/createTableHook.md) +- [createTableState](functions/createTableState.md) +- [renderComponent](functions/renderComponent.md) +- [renderSnippet](functions/renderSnippet.md) +- [subscribeTable](functions/subscribeTable.md) diff --git a/docs/framework/svelte/reference/type-aliases/AppCellContext.md b/docs/framework/svelte/reference/type-aliases/AppCellContext.md new file mode 100644 index 0000000000..6d45e9d72c --- /dev/null +++ b/docs/framework/svelte/reference/type-aliases/AppCellContext.md @@ -0,0 +1,101 @@ +--- +id: AppCellContext +title: AppCellContext +--- + +# Type Alias: AppCellContext\ + +```ts +type AppCellContext = object; +``` + +Defined in: [packages/svelte-table/src/createTableHook.svelte.ts:50](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/createTableHook.svelte.ts#L50) + +Enhanced CellContext with pre-bound cell components. +The `cell` property includes the registered cellComponents. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +## Properties + +### cell + +```ts +cell: Cell & TCellComponents & object; +``` + +Defined in: [packages/svelte-table/src/createTableHook.svelte.ts:56](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/createTableHook.svelte.ts#L56) + +#### Type Declaration + +##### FlexRender + +```ts +FlexRender: typeof FlexRender; +``` + +*** + +### column + +```ts +column: Column; +``` + +Defined in: [packages/svelte-table/src/createTableHook.svelte.ts:58](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/createTableHook.svelte.ts#L58) + +*** + +### getValue + +```ts +getValue: CellContext["getValue"]; +``` + +Defined in: [packages/svelte-table/src/createTableHook.svelte.ts:59](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/createTableHook.svelte.ts#L59) + +*** + +### renderValue + +```ts +renderValue: CellContext["renderValue"]; +``` + +Defined in: [packages/svelte-table/src/createTableHook.svelte.ts:60](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/createTableHook.svelte.ts#L60) + +*** + +### row + +```ts +row: Row; +``` + +Defined in: [packages/svelte-table/src/createTableHook.svelte.ts:61](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/createTableHook.svelte.ts#L61) + +*** + +### table + +```ts +table: Table; +``` + +Defined in: [packages/svelte-table/src/createTableHook.svelte.ts:62](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/createTableHook.svelte.ts#L62) diff --git a/docs/framework/svelte/reference/type-aliases/AppColumnDefBase.md b/docs/framework/svelte/reference/type-aliases/AppColumnDefBase.md new file mode 100644 index 0000000000..90c1425ab3 --- /dev/null +++ b/docs/framework/svelte/reference/type-aliases/AppColumnDefBase.md @@ -0,0 +1,56 @@ +--- +id: AppColumnDefBase +title: AppColumnDefBase +--- + +# Type Alias: AppColumnDefBase\ + +```ts +type AppColumnDefBase = Omit, "cell" | "header" | "footer"> & object; +``` + +Defined in: [packages/svelte-table/src/createTableHook.svelte.ts:95](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/createTableHook.svelte.ts#L95) + +Enhanced column definition base with pre-bound components in cell/header/footer contexts. + +## Type Declaration + +### cell? + +```ts +optional cell: AppColumnDefTemplate>; +``` + +### footer? + +```ts +optional footer: AppColumnDefTemplate>; +``` + +### header? + +```ts +optional header: AppColumnDefTemplate>; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> diff --git a/docs/framework/svelte/reference/type-aliases/AppColumnDefTemplate.md b/docs/framework/svelte/reference/type-aliases/AppColumnDefTemplate.md new file mode 100644 index 0000000000..d4c0a30edc --- /dev/null +++ b/docs/framework/svelte/reference/type-aliases/AppColumnDefTemplate.md @@ -0,0 +1,20 @@ +--- +id: AppColumnDefTemplate +title: AppColumnDefTemplate +--- + +# Type Alias: AppColumnDefTemplate\ + +```ts +type AppColumnDefTemplate = string | (props) => any; +``` + +Defined in: [packages/svelte-table/src/createTableHook.svelte.ts:88](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/createTableHook.svelte.ts#L88) + +Template type for column definitions that can be a string or a function. + +## Type Parameters + +### TProps + +`TProps` *extends* `object` diff --git a/docs/framework/svelte/reference/type-aliases/AppColumnHelper.md b/docs/framework/svelte/reference/type-aliases/AppColumnHelper.md new file mode 100644 index 0000000000..89f3a980fe --- /dev/null +++ b/docs/framework/svelte/reference/type-aliases/AppColumnHelper.md @@ -0,0 +1,144 @@ +--- +id: AppColumnHelper +title: AppColumnHelper +--- + +# Type Alias: AppColumnHelper\ + +```ts +type AppColumnHelper = object; +``` + +Defined in: [packages/svelte-table/src/createTableHook.svelte.ts:171](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/createTableHook.svelte.ts#L171) + +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 Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +## Properties + +### accessor() + +```ts +accessor: (accessor, column) => TAccessor extends AccessorFn ? AccessorFnColumnDef : AccessorKeyColumnDef; +``` + +Defined in: [packages/svelte-table/src/createTableHook.svelte.ts:181](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/createTableHook.svelte.ts#L181) + +Creates a data column definition with an accessor key or function. +The cell, header, and footer contexts include pre-bound components. + +#### Type Parameters + +##### TAccessor + +`TAccessor` *extends* `AccessorFn`\<`TData`\> \| `DeepKeys`\<`TData`\> + +##### TValue + +`TValue` *extends* `TAccessor` *extends* `AccessorFn`\<`TData`, infer TReturn\> ? `TReturn` : `TAccessor` *extends* `DeepKeys`\<`TData`\> ? `DeepValue`\<`TData`, `TAccessor`\> : `never` + +#### Parameters + +##### accessor + +`TAccessor` + +##### column + +`TAccessor` *extends* `AccessorFn`\<`TData`\> ? [`AppColumnDefBase`](AppColumnDefBase.md)\<`TFeatures`, `TData`, `TValue`, `TCellComponents`, `THeaderComponents`\> & `object` : [`AppColumnDefBase`](AppColumnDefBase.md)\<`TFeatures`, `TData`, `TValue`, `TCellComponents`, `THeaderComponents`\> + +#### Returns + +`TAccessor` *extends* `AccessorFn`\<`TData`\> ? `AccessorFnColumnDef`\<`TFeatures`, `TData`, `TValue`\> : `AccessorKeyColumnDef`\<`TFeatures`, `TData`, `TValue`\> + +*** + +### columns() + +```ts +columns: (columns) => ColumnDef[] & [...TColumns]; +``` + +Defined in: [packages/svelte-table/src/createTableHook.svelte.ts:212](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/createTableHook.svelte.ts#L212) + +Wraps an array of column definitions to preserve each column's individual TValue type. + +#### Type Parameters + +##### TColumns + +`TColumns` *extends* `ReadonlyArray`\<`ColumnDef`\<`TFeatures`, `TData`, `any`\>\> + +#### Parameters + +##### columns + +\[`...TColumns`\] + +#### Returns + +`ColumnDef`\<`TFeatures`, `TData`, `any`\>[] & \[`...TColumns`\] + +*** + +### display() + +```ts +display: (column) => DisplayColumnDef; +``` + +Defined in: [packages/svelte-table/src/createTableHook.svelte.ts:220](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/createTableHook.svelte.ts#L220) + +Creates a display column definition for non-data columns. +The cell, header, and footer contexts include pre-bound components. + +#### Parameters + +##### column + +[`AppDisplayColumnDef`](AppDisplayColumnDef.md)\<`TFeatures`, `TData`, `TCellComponents`, `THeaderComponents`\> + +#### Returns + +`DisplayColumnDef`\<`TFeatures`, `TData`, `unknown`\> + +*** + +### group() + +```ts +group: (column) => GroupColumnDef; +``` + +Defined in: [packages/svelte-table/src/createTableHook.svelte.ts:233](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/createTableHook.svelte.ts#L233) + +Creates a group column definition with nested child columns. +The cell, header, and footer contexts include pre-bound components. + +#### Parameters + +##### column + +[`AppGroupColumnDef`](AppGroupColumnDef.md)\<`TFeatures`, `TData`, `TCellComponents`, `THeaderComponents`\> + +#### Returns + +`GroupColumnDef`\<`TFeatures`, `TData`, `unknown`\> diff --git a/docs/framework/svelte/reference/type-aliases/AppDisplayColumnDef.md b/docs/framework/svelte/reference/type-aliases/AppDisplayColumnDef.md new file mode 100644 index 0000000000..a37c325f25 --- /dev/null +++ b/docs/framework/svelte/reference/type-aliases/AppDisplayColumnDef.md @@ -0,0 +1,52 @@ +--- +id: AppDisplayColumnDef +title: AppDisplayColumnDef +--- + +# Type Alias: AppDisplayColumnDef\ + +```ts +type AppDisplayColumnDef = Omit, "cell" | "header" | "footer"> & object; +``` + +Defined in: [packages/svelte-table/src/createTableHook.svelte.ts:119](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/createTableHook.svelte.ts#L119) + +Enhanced display column definition with pre-bound components. + +## Type Declaration + +### cell? + +```ts +optional cell: AppColumnDefTemplate>; +``` + +### footer? + +```ts +optional footer: AppColumnDefTemplate>; +``` + +### header? + +```ts +optional header: AppColumnDefTemplate>; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> diff --git a/docs/framework/svelte/reference/type-aliases/AppGroupColumnDef.md b/docs/framework/svelte/reference/type-aliases/AppGroupColumnDef.md new file mode 100644 index 0000000000..792e485695 --- /dev/null +++ b/docs/framework/svelte/reference/type-aliases/AppGroupColumnDef.md @@ -0,0 +1,58 @@ +--- +id: AppGroupColumnDef +title: AppGroupColumnDef +--- + +# Type Alias: AppGroupColumnDef\ + +```ts +type AppGroupColumnDef = Omit, "cell" | "header" | "footer" | "columns"> & object; +``` + +Defined in: [packages/svelte-table/src/createTableHook.svelte.ts:142](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/createTableHook.svelte.ts#L142) + +Enhanced group column definition with pre-bound components. + +## Type Declaration + +### cell? + +```ts +optional cell: AppColumnDefTemplate>; +``` + +### columns? + +```ts +optional columns: ColumnDef[]; +``` + +### footer? + +```ts +optional footer: AppColumnDefTemplate>; +``` + +### header? + +```ts +optional header: AppColumnDefTemplate>; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> diff --git a/docs/framework/svelte/reference/type-aliases/AppHeaderContext.md b/docs/framework/svelte/reference/type-aliases/AppHeaderContext.md new file mode 100644 index 0000000000..2c675a16ab --- /dev/null +++ b/docs/framework/svelte/reference/type-aliases/AppHeaderContext.md @@ -0,0 +1,71 @@ +--- +id: AppHeaderContext +title: AppHeaderContext +--- + +# Type Alias: AppHeaderContext\ + +```ts +type AppHeaderContext = object; +``` + +Defined in: [packages/svelte-table/src/createTableHook.svelte.ts:69](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/createTableHook.svelte.ts#L69) + +Enhanced HeaderContext with pre-bound header components. +The `header` property includes the registered headerComponents. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +## Properties + +### column + +```ts +column: Column; +``` + +Defined in: [packages/svelte-table/src/createTableHook.svelte.ts:75](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/createTableHook.svelte.ts#L75) + +*** + +### header + +```ts +header: Header & THeaderComponents & object; +``` + +Defined in: [packages/svelte-table/src/createTableHook.svelte.ts:76](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/createTableHook.svelte.ts#L76) + +#### Type Declaration + +##### FlexRender + +```ts +FlexRender: typeof FlexRender; +``` + +*** + +### table + +```ts +table: Table; +``` + +Defined in: [packages/svelte-table/src/createTableHook.svelte.ts:78](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/createTableHook.svelte.ts#L78) diff --git a/docs/framework/svelte/reference/type-aliases/AppSvelteTable.md b/docs/framework/svelte/reference/type-aliases/AppSvelteTable.md new file mode 100644 index 0000000000..215397a913 --- /dev/null +++ b/docs/framework/svelte/reference/type-aliases/AppSvelteTable.md @@ -0,0 +1,131 @@ +--- +id: AppSvelteTable +title: AppSvelteTable +--- + +# Type Alias: AppSvelteTable\ + +```ts +type AppSvelteTable = SvelteTable & NoInfer & object; +``` + +Defined in: [packages/svelte-table/src/createTableHook.svelte.ts:290](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/createTableHook.svelte.ts#L290) + +Extended table API returned by createAppTable with all App wrapper components. + +## Type Declaration + +### AppCell + +```ts +AppCell: Component<{ + cell: Cell; + children: Snippet<[Cell & NoInfer & object]>; +}>; +``` + +Wraps a cell and provides cell context with pre-bound cellComponents. + +#### Example + +```svelte + + {#snippet children(c)} + + {/snippet} + +``` + +### AppFooter + +```ts +AppFooter: Component<{ + children: Snippet<[Header & NoInfer & object]>; + header: Header; +}>; +``` + +Wraps a footer and provides header context with pre-bound headerComponents. + +#### Example + +```svelte + + {#snippet children(f)} + + {/snippet} + +``` + +### AppHeader + +```ts +AppHeader: Component<{ + children: Snippet<[Header & NoInfer & object]>; + header: Header; +}>; +``` + +Wraps a header and provides header context with pre-bound headerComponents. + +#### Example + +```svelte + + {#snippet children(h)} + + {/snippet} + +``` + +### AppTable + +```ts +AppTable: Component<{ + children: Snippet; +}>; +``` + +Root wrapper component that provides table context. + +#### Example + +```svelte + + ...
+
+``` + +### FlexRender + +```ts +FlexRender: typeof FlexRender; +``` + +Convenience FlexRender component attached to the table instance. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TSelected + +`TSelected` + +### TTableComponents + +`TTableComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> diff --git a/docs/framework/svelte/reference/type-aliases/ComponentType.md b/docs/framework/svelte/reference/type-aliases/ComponentType.md new file mode 100644 index 0000000000..a6daf6ee15 --- /dev/null +++ b/docs/framework/svelte/reference/type-aliases/ComponentType.md @@ -0,0 +1,18 @@ +--- +id: ComponentType +title: ComponentType +--- + +# Type Alias: ComponentType\ + +```ts +type ComponentType = Component; +``` + +Defined in: [packages/svelte-table/src/createTableHook.svelte.ts:40](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/createTableHook.svelte.ts#L40) + +## Type Parameters + +### T + +`T` *extends* `Record`\<`string`, `any`\> diff --git a/docs/framework/svelte/reference/type-aliases/CreateTableHookOptions.md b/docs/framework/svelte/reference/type-aliases/CreateTableHookOptions.md new file mode 100644 index 0000000000..193e0686b0 --- /dev/null +++ b/docs/framework/svelte/reference/type-aliases/CreateTableHookOptions.md @@ -0,0 +1,83 @@ +--- +id: CreateTableHookOptions +title: CreateTableHookOptions +--- + +# Type Alias: CreateTableHookOptions\ + +```ts +type CreateTableHookOptions = Omit, "columns" | "data" | "store" | "state" | "initialState"> & object; +``` + +Defined in: [packages/svelte-table/src/createTableHook.svelte.ts:251](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/createTableHook.svelte.ts#L251) + +Options for creating a table hook with pre-bound components and default table options. +Extends all TableOptions except 'columns' | 'data' | 'store' | 'state' | 'initialState'. + +## Type Declaration + +### cellComponents? + +```ts +optional cellComponents: TCellComponents; +``` + +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 + +```ts +{ TextCell, NumberCell, DateCell, CurrencyCell } +``` + +### headerComponents? + +```ts +optional headerComponents: THeaderComponents; +``` + +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 + +```ts +{ SortIndicator, ColumnFilter, ResizeHandle } +``` + +### tableComponents? + +```ts +optional tableComponents: TTableComponents; +``` + +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 + +```ts +{ PaginationControls, GlobalFilter, RowCount } +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TTableComponents + +`TTableComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> diff --git a/docs/framework/svelte/reference/type-aliases/FlexRender.md b/docs/framework/svelte/reference/type-aliases/FlexRender.md new file mode 100644 index 0000000000..93e0b49018 --- /dev/null +++ b/docs/framework/svelte/reference/type-aliases/FlexRender.md @@ -0,0 +1,12 @@ +--- +id: FlexRender +title: FlexRender +--- + +# Type Alias: FlexRender + +```ts +type FlexRender = SvelteComponent; +``` + +Defined in: node\_modules/.pnpm/svelte@5.55.5\_@typescript-eslint+types@8.59.2/node\_modules/svelte/types/index.d.ts:3204 diff --git a/docs/framework/svelte/reference/type-aliases/SubscribeSource.md b/docs/framework/svelte/reference/type-aliases/SubscribeSource.md new file mode 100644 index 0000000000..ed5a22f01e --- /dev/null +++ b/docs/framework/svelte/reference/type-aliases/SubscribeSource.md @@ -0,0 +1,22 @@ +--- +id: SubscribeSource +title: SubscribeSource +--- + +# Type Alias: SubscribeSource\ + +```ts +type SubscribeSource = + | Atom + | ReadonlyAtom + | Store +| ReadonlyStore; +``` + +Defined in: [packages/svelte-table/src/subscribe.ts:9](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/subscribe.ts#L9) + +## Type Parameters + +### TValue + +`TValue` diff --git a/docs/framework/svelte/reference/type-aliases/SvelteTable.md b/docs/framework/svelte/reference/type-aliases/SvelteTable.md new file mode 100644 index 0000000000..682bcc2a4f --- /dev/null +++ b/docs/framework/svelte/reference/type-aliases/SvelteTable.md @@ -0,0 +1,44 @@ +--- +id: SvelteTable +title: SvelteTable +--- + +# Type Alias: SvelteTable\ + +```ts +type SvelteTable = Table & object; +``` + +Defined in: [packages/svelte-table/src/createTable.svelte.ts:14](https://github.com/TanStack/table/blob/main/packages/svelte-table/src/createTable.svelte.ts#L14) + +## Type Declaration + +### state + +```ts +readonly state: Readonly; +``` + +The selected state of the table. This state may not match the structure of `table.store.state` because it is selected by the `selector` function that you pass as the 2nd argument to `createTable`. + +#### Example + +```ts +const table = createTable(options, (state) => ({ globalFilter: state.globalFilter })) // only globalFilter is part of the selected state + +console.log(table.state.globalFilter) +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TSelected + +`TSelected` = `TableState`\<`TFeatures`\> diff --git a/docs/framework/svelte/reference/variables/FlexRender.md b/docs/framework/svelte/reference/variables/FlexRender.md new file mode 100644 index 0000000000..7e447b0564 --- /dev/null +++ b/docs/framework/svelte/reference/variables/FlexRender.md @@ -0,0 +1,12 @@ +--- +id: FlexRender +title: FlexRender +--- + +# Variable: FlexRender + +```ts +const FlexRender: LegacyComponentType; +``` + +Defined in: node\_modules/.pnpm/svelte@5.55.5\_@typescript-eslint+types@8.59.2/node\_modules/svelte/types/index.d.ts:3204 diff --git a/docs/framework/svelte/svelte-table.md b/docs/framework/svelte/svelte-table.md index 412d46ed27..adb1d3c347 100644 --- a/docs/framework/svelte/svelte-table.md +++ b/docs/framework/svelte/svelte-table.md @@ -2,18 +2,172 @@ title: Svelte Table --- -The `@tanstack/svelte-table` adapter is a wrapper around the core table logic. Most of it's job is related to managing state the "svelte" way, providing types and the rendering implementation of cell/header/footer templates. +> **IMPORTANT:** This version of `@tanstack/svelte-table` only supports Svelte 5 or newer. For Svelte 3/4 support, use version 8 of `@tanstack/svelte-table`. -## `createSvelteTable` +The `@tanstack/svelte-table` adapter wraps `@tanstack/table-core` with Svelte 5 rune-based reactivity, rendering helpers, and types. It installs the Svelte `coreReativityFeature` for you, so table atoms can participate in Svelte dependency tracking. -Takes an `options` object and returns a table. +TanStack Table v9 is explicit about what a table uses. Register features with `_features`, and register client-side row model factories with `_rowModels`. The core row model is included by default, so a basic table can use `_rowModels: {}`. + +## Creating a Table + +Use `createTable` to create a Svelte table instance. ```svelte - +``` + +For feature-specific row models, register the feature and put the row model factory under `_rowModels`. -import { createSvelteTable } from '@tanstack/svelte-table' +```svelte + ``` + +## Table State + +Table state is managed with TanStack Store atoms in v9. For most tables, you do not need to manage table state yourself: set `initialState` when you need starting values, and use feature APIs like `table.nextPage()`, `table.setSorting(...)`, and `row.toggleSelected()` instead of mutating state directly. + +Use `atoms` when your app should own one state slice with TanStack Store. Use `state` with the matching `on[State]Change` option for simple Svelte state integration or migration paths. Selected table state is available on `table.state` when you pass a selector to `createTable`. + +```svelte + +``` + +See the [Table State Guide](./guide/table-state.md) for selectors, external atoms, and state ownership patterns. + +## Rendering Headers, Cells, and Footers + +Use `FlexRender` to render column `header`, `cell`, and `footer` definitions. It handles plain values, Svelte components wrapped with `renderComponent`, and snippets wrapped with `renderSnippet`. + +```svelte + + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getVisibleCells() as cell (cell.id)} + + + + {/each} + + {/each} + +``` + +## createTableHook + +`createTableHook` creates an app-specific table creator. Use it when multiple tables should share `_features`, `_rowModels`, default options, column helpers, and component conventions. + +```ts +import { createTableHook, tableFeatures } from '@tanstack/svelte-table' + +const { createAppTable, createAppColumnHelper } = createTableHook({ + _features: tableFeatures({}), + _rowModels: {}, +}) + +const columnHelper = createAppColumnHelper() + +const table = createAppTable({ + columns, + get data() { + return data + }, +}) +``` + +See the [Composable Tables example](./examples/composable-tables) for the full pattern. + +## API Reference + +See the [Svelte API Reference](./reference/index.md). diff --git a/docs/framework/vanilla/guide/table-state.md b/docs/framework/vanilla/guide/table-state.md index d43bd1d74d..d0342b7675 100644 --- a/docs/framework/vanilla/guide/table-state.md +++ b/docs/framework/vanilla/guide/table-state.md @@ -2,4 +2,312 @@ title: Table State (Vanilla JS) Guide --- +## Examples + +Want to skip to the implementation? Check out these examples: + +- [Basic](../examples/basic) +- [Sorting](../examples/sorting) +- [Pagination](../examples/pagination) + ## Table State (Vanilla JS) Guide + +> **If you boil TanStack Table down to one sentence: TanStack Table is a large state-management coordinator for table states.** + +Understanding this guide is fundamental to understanding how TanStack Table works and how to interact with it for the best results. + +### Do you need to Manage External State? + +When you use `@tanstack/table-core` directly, there is no framework adapter to render UI or subscribe a component tree for you. The core table still manages state with TanStack Store atoms, but you decide when to read state, subscribe to state, and redraw your UI. + +You usually do NOT need to manage table state yourself. If you pass nothing to `initialState`, `atoms`, `state`, or any of the `on[State]Change` table options, TanStack Table will manage its own state internally. There will be situations where you need to customize how you interact with the internal table state, or even hoist it up to your own scopes. + +### State in v9 + +TanStack Table v9 overhauled state management around TanStack Store. TanStack Store uses the `alien-signals` implementation and supports performant derived state. In vanilla usage, you provide the core store reactivity bindings explicitly. + +A table instance has a few state surfaces: + +- `table.baseAtoms` are the internal writable atoms created from the resolved initial state. +- `table.atoms` are readonly derived atoms exposed per registered state slice. +- `table.store` is a readonly flat TanStack Store derived by putting all of the registered `table.atoms` together. + +For vanilla or non-framework use, pass `storeReactivityBindings()` through the table's `coreReativityFeature`. Table APIs that call atom `.get()` read current values synchronously. UI updates are your responsibility; subscribe to atoms or `table.store` and redraw as needed. + +### Feature-based State + +State slices are only created for the features that are registered in `_features`. This keeps TanStack Table tree-shakeable and gives TypeScript more accurate state inference. + +```ts +const _features = tableFeatures({ + rowPaginationFeature, + rowSortingFeature, +}) + +const table = constructTable({ + _features: { + coreReativityFeature: storeReactivityBindings(), + ..._features, + }, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, + columns, + data, +}) + +table.atoms.pagination.get() +table.atoms.sorting.get() + +// table.atoms.rowSelection // TypeScript error unless rowSelectionFeature is registered +``` + +If `_features` does not include a feature, its state should not be available in `table.atoms`, `table.store.state`, `initialState`, `state`, or `atoms`. + +### Accessing Table State + +There are two different questions when reading table state: + +- Do you only need the current value? +- Or should your UI redraw when that value changes? + +Use a direct atom or store read for the current value. Use store or atom subscriptions when you want to redraw in response to changes. + +#### Reading State Without Subscribing + +The simplest and most performant way to read a current state value is to read the matching atom: + +```ts +const pagination = table.atoms.pagination.get() +const sorting = table.atoms.sorting.get() +``` + +You can also read the current flat store snapshot: + +```ts +const tableState = table.store.state +const pagination = table.store.state.pagination +``` + +These reads do not redraw anything by themselves. If the UI needs to stay reactive to table state changes, subscribe to `table.store`, subscribe to a specific atom, or use a TanStack Store selector in your own adapter layer. + +#### Reading Reactive State with Store Subscriptions + +Subscribe to `table.store` when your UI should redraw for table state changes: + +```ts +const subscription = table.store.subscribe(() => { + renderTable(table) +}) + +// later +subscription.unsubscribe() +``` + +You can also subscribe to a single atom: + +```ts +const subscription = table.atoms.pagination.subscribe(() => { + renderPagination(table.atoms.pagination.get()) +}) +``` + +### Setting Table State + +You should almost never need to set table state directly. TanStack Table features expose dedicated APIs for interacting with their state, and those APIs are the safest way to make changes. + +```ts +table.nextPage() +table.previousPage() +table.setPageIndex(0) +table.setPageSize(25) +``` + +Use APIs like `table.setSorting(...)`, `table.setColumnFilters(...)`, `column.toggleVisibility()`, or `row.toggleSelected()` instead of manually editing the underlying state object. + +If you only care about setting starting values, use `initialState`. If you want to reset a state slice back to its initial value, use that feature's reset API. + +If you really do need to write a state slice directly, the low-level write surface for internally owned state is the matching base atom: + +```ts +table.baseAtoms.pagination.set((old) => ({ + ...old, + pageIndex: 0, +})) +``` + +Direct base atom writes should be rare. If a slice is owned by an external atom passed through `atoms`, write to that external atom instead; `table.atoms.pagination` will read from the external atom, not the internal base atom. + +### Custom Initial State + +If you only need to customize the starting value for some table state, use `initialState`. You still do not need to manage that state yourself. + +`initialState` only applies to registered state slices. It is used to create the table's initial state and is also used by reset APIs such as `table.resetSorting()` or `table.resetPagination()`. Changing the `initialState` object later does not reset table state. + +```ts +const table = constructTable({ + _features: { + coreReativityFeature: storeReactivityBindings(), + ..._features, + }, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data, + initialState: { + sorting: [ + { + id: 'age', + desc: true, + }, + ], + pagination: { + pageIndex: 0, + pageSize: 25, + }, + }, +}) +``` + +> **Note:** Do not provide the same state slice in multiple ownership places unless you intentionally want one to win. For a slice like `pagination`, prefer exactly one of `initialState.pagination`, `atoms.pagination`, or `state.pagination` as the source of truth. External atoms take precedence over external `state`; external `state` syncs into the table's internal base atom. + +#### Resetting to Initial State + +Feature reset APIs reset to `table.initialState` by default. Many reset APIs also accept `true` to reset to that feature's blank/default state instead: + +```ts +table.resetSorting() +table.resetPagination() +table.resetPagination(true) +``` + +Slice reset APIs like `resetPagination()` update through that feature's state updater and can update an externally owned atom. The core `table.reset()` API resets the internal base atoms, so do not use it as the primary way to reset state that is owned by external atoms. + +### Controlled State + +If you need easy access to table state outside the table, you can control individual state slices. In vanilla usage, external atoms are usually the cleanest way to share table state between the table and your own UI code. + +#### External Atoms + +Use external atoms when the app should own one or more table state slices. Create stable writable atoms with `createAtom` from `@tanstack/store`, pass them to `atoms`, and subscribe or read from them wherever your app needs the value. + +```ts +import { createAtom } from '@tanstack/store' +import { + constructTable, + rowPaginationFeature, + tableFeatures, + type PaginationState, +} from '@tanstack/table-core' +import { storeReactivityBindings } from '@tanstack/table-core/store-reactivity-bindings' + +const _features = tableFeatures({ + rowPaginationFeature, +}) + +const paginationAtom = createAtom({ + pageIndex: 0, + pageSize: 10, +}) + +const table = constructTable({ + _features: { + coreReativityFeature: storeReactivityBindings(), + ..._features, + }, + _rowModels: {}, + columns, + data, + atoms: { + pagination: paginationAtom, + }, + manualPagination: true, +}) + +paginationAtom.subscribe(() => { + renderPagination(paginationAtom.get()) +}) +``` + +When using the `atoms` option for a slice, you do not need to add the matching `on[State]Change` option. + +#### External State + +The classic `state` plus `on[State]Change` pattern is still supported. This can be convenient when you already have a plain external state object, but it is less atomic than external atoms. + +```ts +let pagination: PaginationState = { + pageIndex: 0, + pageSize: 10, +} + +const table = constructTable({ + _features: { + coreReativityFeature: storeReactivityBindings(), + ..._features, + }, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data, + state: { + pagination, + }, + onPaginationChange: (updater) => { + pagination = updater instanceof Function ? updater(pagination) : updater + table.setOptions((prev) => ({ + ...prev, + state: { + ...prev.state, + pagination, + }, + })) + renderTable(table) + }, +}) +``` + +The v8-style `onStateChange` option is no longer part of the v9 core state model. v9 encourages keeping table state slices atomic and separated for performance. + +##### On State Change Callbacks + +The `on[State]Change` callbacks are useful when you are controlling a matching slice through the `state` option. They receive either a raw value or an updater function. + +If you provide an `on[State]Change` callback, also provide the corresponding value in `state`. For example, `onSortingChange` should be paired with `state.sorting`. + +```ts +onPaginationChange: (updater) => { + pagination = updater instanceof Function ? updater(pagination) : updater +} +``` + +### State Types + +Most complex states in TanStack Table have their own TypeScript types that you can import and use. + +```ts +import { + constructTable, + type PaginationState, + type RowSelectionState, + type SortingState, + type TableState, +} from '@tanstack/table-core' + +let sorting: SortingState = [ + { + id: 'age', + desc: true, + }, +] +``` + +`TableState` is inferred from the features registered on that table: + +```ts +type MyTableState = TableState +``` diff --git a/docs/framework/vue/guide/table-state.md b/docs/framework/vue/guide/table-state.md index aab44cd4fc..f5bad09525 100644 --- a/docs/framework/vue/guide/table-state.md +++ b/docs/framework/vue/guide/table-state.md @@ -2,247 +2,346 @@ title: Table State (Vue) Guide --- +## Examples + +Want to skip to the implementation? Check out these examples: + +- [Basic useTable](../examples/basic-use-table) +- [Basic External Atoms](../examples/basic-external-atoms) +- [Basic External State](../examples/basic-external-state) +- [With TanStack Query](../examples/with-tanstack-query) + ## Table State (Vue) Guide -TanStack Table has a simple underlying internal state management system to store and manage the state of the table. It also lets you selectively pull out any state that you need to manage in your own state management. This guide will walk you through the different ways in which you can interact with and manage the state of the table. +> **If you boil TanStack Table down to one sentence: TanStack Table is a large state-management coordinator for table states.** -### Accessing Table State +Understanding this guide is fundamental to understanding how TanStack Table works and how to interact with it for the best results. + +### Do you need to Manage External State? + +You usually do NOT need to manage table state yourself. If you pass nothing to `initialState`, `atoms`, `state`, or any of the `on[State]Change` table options, TanStack Table will manage its own state internally. + +There will be situations where you need to customize how you interact with the internal table state, or even hoist it up to your own scopes. TanStack Table lets you read, subscribe to, or own the state slices that matter to your app. This guide explains how table state works in Vue, how to read it, and when to use external atoms or external state. + +### State in v9 -You do not need to set up anything special in order for the table state to work. If you pass nothing into either `state`, `initialState`, or any of the `on[State]Change` table options, the table will manage its own state internally. You can access any part of this internal state by using the `table.getState()` table instance API. +TanStack Table v9 overhauled state management around TanStack Store. TanStack Store uses the `alien-signals` implementation and supports performant derived state. For Vue, the table adapter supplies custom reactivity so table state atoms are backed by Vue refs and computed values. + +A table instance has a few state surfaces: + +- `table.baseAtoms` are the internal writable atoms created from the resolved initial state. +- `table.atoms` are readonly derived atoms exposed per registered state slice. +- `table.store` is a readonly flat TanStack Store derived by putting all of the registered `table.atoms` together. +- `table.state` is Vue-only selected state. It is the value returned from the selector passed as the second argument to `useTable`. + +The Vue adapter provides `vueReactivity()` to the table's `coreReativityFeature`. Core readonly atoms are Vue `computed` values, writable atoms are `shallowRef` values, and subscriptions are backed by `watch(..., { flush: 'sync' })`. `useTable` also watches reactive option dependencies and controlled state values so it can call `table.setOptions` when Vue state changes. + +### Feature-based State + +State slices are only created for the features that are registered in `_features`. This keeps TanStack Table tree-shakeable and gives TypeScript more accurate state inference. ```ts -const table = useVueTable({ +const _features = tableFeatures({ + rowPaginationFeature, + rowSortingFeature, +}) + +const table = useTable({ + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, columns, - data: dataRef, // Reactive data support - //... + data, }) -console.log(table.getState()) //access the entire internal state -console.log(table.getState().rowSelection) //access just the row selection state +table.atoms.pagination.get() +table.atoms.sorting.get() + +// table.atoms.rowSelection // TypeScript error unless rowSelectionFeature is registered ``` -### Using Reactive Data +If `_features` does not include a feature, its state should not be available in `table.atoms`, `table.store.state`, `table.state`, `initialState`, `state`, or `atoms`. + +### Accessing Table State + +There are two different questions when reading table state: + +- Do you only need the current value? +- Or should Vue render or computed work update when that value changes? -> **New in v8.20.0** +Use a direct atom or store read for the current value. Use selected state, `useSelector`, or `table.Subscribe` when you want Vue to track the selected value. -The `useVueTable` hook now supports reactive data. This means you can pass a Vue `ref` or `computed` containing your data to the `data`-option. The table will automatically react to changes in the data. +#### Reading State Without Subscribing + +The simplest and most performant way to read a current state value is to read the matching atom: ```ts -const columns = [ - { accessor: 'id', Header: 'ID' }, - { accessor: 'name', Header: 'Name' } -] - -const dataRef = ref([ - { id: 1, name: 'John' }, - { id: 2, name: 'Jane' } -]) +const pagination = table.atoms.pagination.get() +const sorting = table.atoms.sorting.get() +``` + +You can also read the current flat store snapshot: + +```ts +const tableState = table.store.state +const pagination = table.store.state.pagination +``` -const table = useVueTable({ +These reads are current-value reads. They only participate in Vue dependency tracking when they are called in a Vue reactive context that tracks those reads. If the UI needs to stay reactive to table state changes, use `table.state`, `table.Subscribe`, or even a `useSelector` hook from TanStack Store. + +#### Reading Reactive State with useTable + +The second argument to `useTable` is a TanStack Store selector. The selected value is exposed as `table.state`. The default selector selects all registered table state. + +```ts +const table = useTable( + { + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data, + }, + (state) => ({ + pagination: state.pagination, + }), +) + +table.state.pagination +``` + +Vue's `data` option can also be a `ref` or `computed`. The adapter unwraps reactive option values and syncs the table when those values change. + +```ts +const data = ref(makeData(100)) + +const table = useTable({ + _features, + _rowModels: {}, columns, - data: dataRef, // Pass the reactive data ref + data, }) -// Later, updating dataRef will automatically update the table -dataRef.value = [ - { id: 1, name: 'John' }, - { id: 2, name: 'Jane' }, - { id: 3, name: 'Doe' } -] +data.value = makeData(200) ``` -> ⚠️ `shallowRef` is used under the hood for performance reasons, meaning that the data is not deeply reactive, only the `.value` is. To update the data you have to mutate the data directly. +#### Fine-grained Updates with table.Subscribe + +Use `table.Subscribe` in render functions or JSX when you want a specific part of the Vue tree to subscribe to a selected table state value. + +Without a `source` prop, `table.Subscribe` subscribes to `table.store` and requires a selector. With a `source` prop, it can subscribe directly to one atom or store. + +```tsx + ({ + columnFilters: state.columnFilters, + globalFilter: state.globalFilter, + pagination: state.pagination, + })} +> + {() => ( + + {table.getRowModel().rows.map((row) => ( + ... + ))} + + )} + +``` + +### Setting Table State + +You should almost never need to set table state directly. TanStack Table features expose dedicated APIs for interacting with their state, and those APIs are the safest way to make changes. ```ts -const dataRef = ref([ - { id: 1, name: 'John' }, - { id: 2, name: 'Jane' } -]) +table.nextPage() +table.previousPage() +table.setPageIndex(0) +table.setPageSize(25) +``` + +Use APIs like `table.setSorting(...)`, `table.setColumnFilters(...)`, `column.toggleVisibility()`, or `row.toggleSelected()` instead of manually editing the underlying state object. + +If you only care about setting starting values, use `initialState`. If you want to reset a state slice back to its initial value, use that feature's reset API. -// This will NOT update the table ❌ -dataRef.value.push({ id: 4, name: 'John' }) +If you really do need to write a state slice directly, the low-level write surface for internally owned state is the matching base atom: -// This will update the table ✅ -dataRef.value = [ - ...dataRef.value, - { id: 4, name: 'John' } -] +```ts +table.baseAtoms.pagination.set((old) => ({ + ...old, + pageIndex: 0, +})) ``` +Direct base atom writes should be rare. If a slice is owned by an external atom passed through `atoms`, write to that external atom instead; `table.atoms.pagination` will read from the external atom, not the internal base atom. + ### Custom Initial State -If all you need to do for certain states is customize their initial default values, you still do not need to manage any of the state yourself. You can simply set values in the `initialState` option of the table instance. +If you only need to customize the starting value for some table state, use `initialState`. You still do not need to manage that state yourself. -```jsx -const table = useVueTable({ +`initialState` only applies to registered state slices. It is used to create the table's initial state and is also used by reset APIs such as `table.resetSorting()` or `table.resetPagination()`. Changing the `initialState` object later does not reset table state. + +```ts +const table = useTable({ + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, columns, data, initialState: { - columnOrder: ['age', 'firstName', 'lastName'], //customize the initial column order - columnVisibility: { - id: false //hide the id column by default - }, - expanded: true, //expand all rows by default sorting: [ { id: 'age', - desc: true //sort by age in descending order by default - } - ] + desc: true, + }, + ], + pagination: { + pageIndex: 0, + pageSize: 25, + }, }, - //... }) ``` -> **Note**: Only specify each particular state in either `initialState` or `state`, but not both. If you pass in a particular state value to both `initialState` and `state`, the initialized state in `state` will take overwrite any corresponding value in `initialState`. +> **Note:** Do not provide the same state slice in multiple ownership places unless you intentionally want one to win. For a slice like `pagination`, prefer exactly one of `initialState.pagination`, `atoms.pagination`, or `state.pagination` as the source of truth. External atoms take precedence over external `state`; external `state` syncs into the table's internal base atom. -### Controlled State +#### Resetting to Initial State + +Feature reset APIs reset to `table.initialState` by default. Many reset APIs also accept `true` to reset to that feature's blank/default state instead: -If you need easy access to the table state in other areas of your application, TanStack Table makes it easy to control and manage any or all of the table state in your own state management system. You can do this by passing in your own state and state management functions to the `state` and `on[State]Change` table options. +```ts +table.resetSorting() +table.resetPagination() +table.resetPagination(true) +``` -#### Individual Controlled State +Slice reset APIs like `resetPagination()` update through that feature's state updater and can update an externally owned atom. The core `table.reset()` API resets the internal base atoms, so do not use it as the primary way to reset state that is owned by external atoms. + +### Controlled State -You can control just the state that you need easy access to. You do NOT have to control all of the table state if you do not need to. It is recommended to only control the state that you need on a case-by-case basis. +If you need easy access to table state in other parts of your application, you can control individual state slices. In v9, external atoms are the recommended way to do this when you want atomic ownership and fine-grained Vue updates. -In order to control a particular state, you need to both pass in the corresponding `state` value and the `on[State]Change` function to the table instance. +#### External Atoms -Let's take filtering, sorting, and pagination as an example in a "manual" server-side data fetching scenario. You can store the filtering, sorting, and pagination state in your own state management, but leave out any other state like column order, column visibility, etc. if your API does not care about those values. +Use external atoms when the app should own one or more table state slices. Create stable writable atoms with `createAtom`, pass them to `atoms`, and subscribe to them with `useSelector`. ```ts -const columnFilters = ref([]) //no default filters -const sorting = ref([{ - id: 'age', - desc: true, //sort by age in descending order by default -}]) -const pagination = ref({ pageIndex: 0, pageSize: 15 } - -//Use our controlled state values to fetch data -const tableQuery = useQuery({ - queryKey: ['users', columnFilters, sorting, pagination], - queryFn: () => fetchUsers(columnFilters, sorting, pagination), - //... +import { createAtom, useSelector } from '@tanstack/vue-store' +import { + rowPaginationFeature, + tableFeatures, + useTable, + type PaginationState, +} from '@tanstack/vue-table' + +const _features = tableFeatures({ + rowPaginationFeature, }) -const table = useVueTable({ - columns, - data: tableQuery.data, - //... - state: { - get columnFilters() { - return columnFilters.value - }, - get sorting() { - return sorting.value - }, - get pagination() { - return pagination.value - } - }, - onColumnFiltersChange: updater => { - columnFilters.value = - updater instanceof Function - ? updater(columnFilters.value) - : updater - }, - onSortingChange: updater => { - sorting.value = - updater instanceof Function - ? updater(sorting.value) - : updater - }, - onPaginationChange: updater => { - pagination.value = - updater instanceof Function - ? updater(pagination.value) - : updater - }, +const paginationAtom = createAtom({ + pageIndex: 0, + pageSize: 10, }) -//... -``` -#### Fully Controlled State +const pagination = useSelector(paginationAtom) -Alternatively, you can control the entire table state with the `onStateChange` table option. It will hoist out the entire table state into your own state management system. Be careful with this approach, as you might find that raising some frequently changing state values up a react tree, like `columnSizingInfo` state`, might cause bad performance issues. - -A couple of more tricks may be needed to make this work. If you use the `onStateChange` table option, the initial values of the `state` must be populated with all of the relevant state values for all of the features that you want to use. You can either manually type out all of the initial state values, or use the `table.setOptions` API in a special way as shown below. - -```jsx -//create a table instance with default state values -const table = useVueTable({ - get columns() { - return columns.value +const table = useTable({ + _features, + _rowModels: {}, + columns, + data: tableData, + rowCount, + atoms: { + pagination: paginationAtom, }, - data, - //... Note: `state` values are NOT passed in yet -}) - -const state = ref({ - ...table.initialState, - pagination: { - pageIndex: 0, - pageSize: 15 - } + manualPagination: true, }) -const setState = updater => { - state.value = updater instanceof Function ? updater(state.value) : updater -} -//Use the table.setOptions API to merge our fully controlled state onto the table instance -table.setOptions(prev => ({ - ...prev, //preserve any other options that we have set up above - get state() { - return state.value - }, - onStateChange: setState //any state changes will be pushed up to our own state management -})) +// pagination.value is reactive, and table pagination APIs update paginationAtom ``` -### On State Change Callbacks +When using the `atoms` option for a slice, you do not need to add the matching `on[State]Change` option. -So far, we have seen the `on[State]Change` and `onStateChange` table options work to "hoist" the table state changes into our own state management. However, there are a few things about these using these options that you should be aware of. +#### External State -#### 1. **State Change Callbacks MUST have their corresponding state value in the `state` option**. +The classic `state` plus `on[State]Change` pattern is still supported. This can be convenient for simple integrations or when migrating v8 code, but it is less atomic than external atoms. -Specifying an `on[State]Change` callback tells the table instance that this will be a controlled state. If you do not specify the corresponding `state` value, that state will be "frozen" with its initial value. +```ts +const sorting = ref([]) +const pagination = ref({ + pageIndex: 0, + pageSize: 10, +}) -```jsx -const sorting = ref([]) -const setSorting = updater => { - sorting.value = updater instanceof Function ? updater(sorting.value) : updater -} -//... -const table = useVueTable({ +const table = useTable({ + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, columns, data, - //... state: { get sorting() { - return sorting //required because we are using `onSortingChange` + return sorting.value + }, + get pagination() { + return pagination.value }, }, - onSortingChange: setSorting, //makes the `state.sorting` controlled + onSortingChange: (updater) => { + sorting.value = updater instanceof Function ? updater(sorting.value) : updater + }, + onPaginationChange: (updater) => { + pagination.value = + updater instanceof Function ? updater(pagination.value) : updater + }, }) ``` -#### 2. **Updaters can either be raw values or callback functions**. +The v8-style `onStateChange` option is no longer part of the v9 `useTable` state model. v9 encourages keeping table state slices atomic and separated for performance. + +##### On State Change Callbacks -The `on[State]Change` and `onStateChange` callbacks work exactly like the `setState` functions in React. The updater values can either be a new state value or a callback function that takes the previous state value and returns the new state value. +The `on[State]Change` callbacks are useful when you are controlling a matching slice through the `state` option. They receive either a raw value or an updater function. -What implications does this have? It means that if you want to add in some extra logic in any of the `on[State]Change` callbacks, you can do so, but you need to check whether or not the new incoming updater value is a function or value. +If you provide an `on[State]Change` callback, also provide the corresponding value in `state`. For example, `onSortingChange` should be paired with `state.sorting`. -This is why we have the `updater instanceof Function` check in the `setState` functions above. This check allows us to handle both raw values and callback functions in the same function. +```ts +onPaginationChange: (updater) => { + pagination.value = + updater instanceof Function ? updater(pagination.value) : updater +} +``` ### State Types -All complex states in TanStack Table have their own TypeScript types that you can import and use. This can be handy for ensuring that you are using the correct data structures and properties for the state values that you are controlling. +Most complex states in TanStack Table have their own TypeScript types that you can import and use. -```tsx -import { useVueTable, type SortingState } from '@tanstack/vue-table' -//... -const sorting = ref([ +```ts +import { + useTable, + type PaginationState, + type RowSelectionState, + type SortingState, + type TableState, +} from '@tanstack/vue-table' + +const sorting = ref([ { - id: 'age', //you should get autocomplete for the `id` and `desc` properties + id: 'age', desc: true, - } + }, ]) ``` + +`TableState` is inferred from the features registered on that table: + +```ts +type MyTableState = TableState +``` diff --git a/docs/framework/vue/reference/functions/createTableHook.md b/docs/framework/vue/reference/functions/createTableHook.md new file mode 100644 index 0000000000..3bf4a5427b --- /dev/null +++ b/docs/framework/vue/reference/functions/createTableHook.md @@ -0,0 +1,158 @@ +--- +id: createTableHook +title: createTableHook +--- + +# Function: createTableHook() + +```ts +function createTableHook(__namedParameters): object; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:300](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L300) + +Creates app-scoped Vue table helpers with features, row models, and +renderable component maps pre-bound. + +Use this when an app or design system wants typed `useAppTable`, a pre-bound +column helper, and context helpers for table, cell, and header components. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TTableComponents + +`TTableComponents` *extends* `Record`\<`string`, [`ComponentType`](../type-aliases/ComponentType.md)\<`any`\>\> + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](../type-aliases/ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](../type-aliases/ComponentType.md)\<`any`\>\> + +## Parameters + +### \_\_namedParameters + +[`CreateTableHookOptions`](../type-aliases/CreateTableHookOptions.md)\<`TFeatures`, `TTableComponents`, `TCellComponents`, `THeaderComponents`\> + +## Returns + +`object` + +### appFeatures + +```ts +appFeatures: TFeatures; +``` + +### createAppColumnHelper() + +```ts +createAppColumnHelper: () => AppColumnHelper; +``` + +#### Type Parameters + +##### TData + +`TData` *extends* `RowData` + +#### Returns + +[`AppColumnHelper`](../type-aliases/AppColumnHelper.md)\<`TFeatures`, `TData`, `TCellComponents`, `THeaderComponents`\> + +### useAppTable() + +```ts +useAppTable: (tableOptions, selector?) => AppVueTable; +``` + +#### Type Parameters + +##### TData + +`TData` *extends* `RowData` + +##### TSelected + +`TSelected` = `TableState`\<`TFeatures`\> + +#### Parameters + +##### tableOptions + +`Omit`\<[`TableOptionsWithReactiveData`](../type-aliases/TableOptionsWithReactiveData.md)\<`TFeatures`, `TData`\>, `"_features"` \| `"_rowModels"`\> + +##### selector? + +(`state`) => `TSelected` + +#### Returns + +[`AppVueTable`](../type-aliases/AppVueTable.md)\<`TFeatures`, `TData`, `TSelected`, `TTableComponents`, `TCellComponents`, `THeaderComponents`\> + +### useCellContext() + +```ts +useCellContext: () => Cell; +``` + +#### Type Parameters + +##### TValue + +`TValue` *extends* `unknown` = `unknown` + +#### Returns + +`Cell`\<`TFeatures`, `any`, `TValue`\> + +### useHeaderContext() + +```ts +useHeaderContext: () => Header; +``` + +#### Type Parameters + +##### TValue + +`TValue` *extends* `unknown` = `unknown` + +#### Returns + +`Header`\<`TFeatures`, `any`, `TValue`\> + +### useTableContext() + +```ts +useTableContext: () => VueTable; +``` + +#### Type Parameters + +##### TData + +`TData` *extends* `RowData` = `RowData` + +#### Returns + +[`VueTable`](../type-aliases/VueTable.md)\<`TFeatures`, `TData`\> + +## Example + +```ts +const { useAppTable, createAppColumnHelper } = createTableHook({ + _features, + _rowModels: {}, + tableComponents: {}, + cellComponents: {}, + headerComponents: {}, +}) +``` diff --git a/docs/framework/vue/reference/functions/flexRender.md b/docs/framework/vue/reference/functions/flexRender.md new file mode 100644 index 0000000000..5a34ace877 --- /dev/null +++ b/docs/framework/vue/reference/functions/flexRender.md @@ -0,0 +1,34 @@ +--- +id: flexRender +title: flexRender +--- + +# Function: flexRender() + +```ts +function flexRender(render, props): any; +``` + +Defined in: [packages/vue-table/src/FlexRender.ts:15](https://github.com/TanStack/table/blob/main/packages/vue-table/src/FlexRender.ts#L15) + +If rendering headers, cells, or footers with custom markup, use flexRender instead of `cell.getValue()` or `cell.renderValue()`. + +## Parameters + +### render + +`any` + +### props + +`any` + +## Returns + +`any` + +## Example + +```ts +flexRender(cell.column.columnDef.cell, cell.getContext()) +``` diff --git a/docs/framework/vue/reference/functions/useTable.md b/docs/framework/vue/reference/functions/useTable.md new file mode 100644 index 0000000000..2c37444bf1 --- /dev/null +++ b/docs/framework/vue/reference/functions/useTable.md @@ -0,0 +1,63 @@ +--- +id: useTable +title: useTable +--- + +# Function: useTable() + +```ts +function useTable(tableOptions, selector?): VueTable; +``` + +Defined in: [packages/vue-table/src/useTable.ts:130](https://github.com/TanStack/table/blob/main/packages/vue-table/src/useTable.ts#L130) + +Creates a Vue table instance backed by Vue-aware TanStack Store atoms. + +Table options may contain Vue refs or computed values. The adapter unwraps +those reactive inputs, watches them with synchronous flushing, and keeps the +table options in sync. The optional selector projects from `table.store` and +exposes the selected value on `table.state`. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TSelected + +`TSelected` = `TableState`\<`TFeatures`\> + +## Parameters + +### tableOptions + +`TableOptions`\<`TFeatures`, `TData`\> | [`TableOptionsWithReactiveData`](../type-aliases/TableOptionsWithReactiveData.md)\<`TFeatures`, `TData`\> + +### selector? + +(`state`) => `TSelected` + +## Returns + +[`VueTable`](../type-aliases/VueTable.md)\<`TFeatures`, `TData`, `TSelected`\> + +## Example + +```ts +const table = useTable( + { + _features, + _rowModels: {}, + columns, + data, + }, + (state) => ({ pagination: state.pagination }), +) + +table.state.pagination +``` diff --git a/docs/framework/vue/reference/index.md b/docs/framework/vue/reference/index.md new file mode 100644 index 0000000000..de56c21f33 --- /dev/null +++ b/docs/framework/vue/reference/index.md @@ -0,0 +1,39 @@ +--- +id: "@tanstack/vue-table" +title: "@tanstack/vue-table" +--- + +# @tanstack/vue-table + +## Interfaces + +- [AppCellProps](interfaces/AppCellProps.md) +- [AppHeaderProps](interfaces/AppHeaderProps.md) +- [AppTableProps](interfaces/AppTableProps.md) + +## Type Aliases + +- [AppCellContext](type-aliases/AppCellContext.md) +- [AppColumnDefBase](type-aliases/AppColumnDefBase.md) +- [AppColumnDefTemplate](type-aliases/AppColumnDefTemplate.md) +- [AppColumnHelper](type-aliases/AppColumnHelper.md) +- [AppDisplayColumnDef](type-aliases/AppDisplayColumnDef.md) +- [AppGroupColumnDef](type-aliases/AppGroupColumnDef.md) +- [AppHeaderContext](type-aliases/AppHeaderContext.md) +- [AppVueTable](type-aliases/AppVueTable.md) +- [ComponentType](type-aliases/ComponentType.md) +- [CreateTableHookOptions](type-aliases/CreateTableHookOptions.md) +- [SubscribeSource](type-aliases/SubscribeSource.md) +- [TableOptionsWithReactiveData](type-aliases/TableOptionsWithReactiveData.md) +- [VueTable](type-aliases/VueTable.md) + +## Variables + +- [AppFlexRender](variables/AppFlexRender.md) +- [FlexRender](variables/FlexRender.md) + +## Functions + +- [createTableHook](functions/createTableHook.md) +- [flexRender](functions/flexRender.md) +- [useTable](functions/useTable.md) diff --git a/docs/framework/vue/reference/interfaces/AppCellProps.md b/docs/framework/vue/reference/interfaces/AppCellProps.md new file mode 100644 index 0000000000..cb4f5fcc1e --- /dev/null +++ b/docs/framework/vue/reference/interfaces/AppCellProps.md @@ -0,0 +1,56 @@ +--- +id: AppCellProps +title: AppCellProps +--- + +# Interface: AppCellProps\ + +Defined in: [packages/vue-table/src/createTableHook.ts:202](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L202) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` = `CellData` + +### TSelected + +`TSelected` = `unknown` + +## Properties + +### cell + +```ts +cell: Cell; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:208](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L208) + +*** + +### selector()? + +```ts +optional selector: (state) => TSelected; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:209](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L209) + +#### Parameters + +##### state + +`TableState`\<`TFeatures`\> + +#### Returns + +`TSelected` diff --git a/docs/framework/vue/reference/interfaces/AppHeaderProps.md b/docs/framework/vue/reference/interfaces/AppHeaderProps.md new file mode 100644 index 0000000000..584d7805dc --- /dev/null +++ b/docs/framework/vue/reference/interfaces/AppHeaderProps.md @@ -0,0 +1,56 @@ +--- +id: AppHeaderProps +title: AppHeaderProps +--- + +# Interface: AppHeaderProps\ + +Defined in: [packages/vue-table/src/createTableHook.ts:212](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L212) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` = `CellData` + +### TSelected + +`TSelected` = `unknown` + +## Properties + +### header + +```ts +header: Header; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:218](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L218) + +*** + +### selector()? + +```ts +optional selector: (state) => TSelected; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:219](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L219) + +#### Parameters + +##### state + +`TableState`\<`TFeatures`\> + +#### Returns + +`TSelected` diff --git a/docs/framework/vue/reference/interfaces/AppTableProps.md b/docs/framework/vue/reference/interfaces/AppTableProps.md new file mode 100644 index 0000000000..3b94e4417d --- /dev/null +++ b/docs/framework/vue/reference/interfaces/AppTableProps.md @@ -0,0 +1,38 @@ +--- +id: AppTableProps +title: AppTableProps +--- + +# Interface: AppTableProps\ + +Defined in: [packages/vue-table/src/createTableHook.ts:195](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L195) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TSelected + +`TSelected` = `unknown` + +## Properties + +### selector()? + +```ts +optional selector: (state) => TSelected; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:199](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L199) + +#### Parameters + +##### state + +`TableState`\<`TFeatures`\> + +#### Returns + +`TSelected` diff --git a/docs/framework/vue/reference/type-aliases/AppCellContext.md b/docs/framework/vue/reference/type-aliases/AppCellContext.md new file mode 100644 index 0000000000..5ef2bad0e3 --- /dev/null +++ b/docs/framework/vue/reference/type-aliases/AppCellContext.md @@ -0,0 +1,98 @@ +--- +id: AppCellContext +title: AppCellContext +--- + +# Type Alias: AppCellContext\ + +```ts +type AppCellContext = object; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:34](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L34) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +## Properties + +### cell + +```ts +cell: Cell & TCellComponents & object; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:40](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L40) + +#### Type Declaration + +##### FlexRender + +```ts +FlexRender: Component; +``` + +*** + +### column + +```ts +column: Column; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:42](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L42) + +*** + +### getValue + +```ts +getValue: CellContext["getValue"]; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:43](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L43) + +*** + +### renderValue + +```ts +renderValue: CellContext["renderValue"]; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:44](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L44) + +*** + +### row + +```ts +row: Row; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:45](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L45) + +*** + +### table + +```ts +table: Table; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:46](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L46) diff --git a/docs/framework/vue/reference/type-aliases/AppColumnDefBase.md b/docs/framework/vue/reference/type-aliases/AppColumnDefBase.md new file mode 100644 index 0000000000..38a893d275 --- /dev/null +++ b/docs/framework/vue/reference/type-aliases/AppColumnDefBase.md @@ -0,0 +1,54 @@ +--- +id: AppColumnDefBase +title: AppColumnDefBase +--- + +# Type Alias: AppColumnDefBase\ + +```ts +type AppColumnDefBase = Omit, "cell" | "header" | "footer"> & object; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:65](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L65) + +## Type Declaration + +### cell? + +```ts +optional cell: AppColumnDefTemplate>; +``` + +### footer? + +```ts +optional footer: AppColumnDefTemplate>; +``` + +### header? + +```ts +optional header: AppColumnDefTemplate>; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> diff --git a/docs/framework/vue/reference/type-aliases/AppColumnDefTemplate.md b/docs/framework/vue/reference/type-aliases/AppColumnDefTemplate.md new file mode 100644 index 0000000000..51a7f1ee62 --- /dev/null +++ b/docs/framework/vue/reference/type-aliases/AppColumnDefTemplate.md @@ -0,0 +1,18 @@ +--- +id: AppColumnDefTemplate +title: AppColumnDefTemplate +--- + +# Type Alias: AppColumnDefTemplate\ + +```ts +type AppColumnDefTemplate = string | (props) => any; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:61](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L61) + +## Type Parameters + +### TProps + +`TProps` *extends* `object` diff --git a/docs/framework/vue/reference/type-aliases/AppColumnHelper.md b/docs/framework/vue/reference/type-aliases/AppColumnHelper.md new file mode 100644 index 0000000000..48a4dbf58f --- /dev/null +++ b/docs/framework/vue/reference/type-aliases/AppColumnHelper.md @@ -0,0 +1,130 @@ +--- +id: AppColumnHelper +title: AppColumnHelper +--- + +# Type Alias: AppColumnHelper\ + +```ts +type AppColumnHelper = object; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:127](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L127) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +## Properties + +### accessor() + +```ts +accessor: (accessor, column) => TAccessor extends AccessorFn ? AccessorFnColumnDef : AccessorKeyColumnDef; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:133](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L133) + +#### Type Parameters + +##### TAccessor + +`TAccessor` *extends* `AccessorFn`\<`TData`\> \| `DeepKeys`\<`TData`\> + +##### TValue + +`TValue` *extends* `TAccessor` *extends* `AccessorFn`\<`TData`, infer TReturn\> ? `TReturn` : `TAccessor` *extends* `DeepKeys`\<`TData`\> ? `DeepValue`\<`TData`, `TAccessor`\> : `never` + +#### Parameters + +##### accessor + +`TAccessor` + +##### column + +`TAccessor` *extends* `AccessorFn`\<`TData`\> ? [`AppColumnDefBase`](AppColumnDefBase.md)\<`TFeatures`, `TData`, `TValue`, `TCellComponents`, `THeaderComponents`\> & `object` : [`AppColumnDefBase`](AppColumnDefBase.md)\<`TFeatures`, `TData`, `TValue`, `TCellComponents`, `THeaderComponents`\> + +#### Returns + +`TAccessor` *extends* `AccessorFn`\<`TData`\> ? `AccessorFnColumnDef`\<`TFeatures`, `TData`, `TValue`\> : `AccessorKeyColumnDef`\<`TFeatures`, `TData`, `TValue`\> + +*** + +### columns() + +```ts +columns: (columns) => ColumnDef[] & [...TColumns]; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:160](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L160) + +#### Type Parameters + +##### TColumns + +`TColumns` *extends* `ReadonlyArray`\<`ColumnDef`\<`TFeatures`, `TData`, `any`\>\> + +#### Parameters + +##### columns + +\[`...TColumns`\] + +#### Returns + +`ColumnDef`\<`TFeatures`, `TData`, `any`\>[] & \[`...TColumns`\] + +*** + +### display() + +```ts +display: (column) => DisplayColumnDef; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:163](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L163) + +#### Parameters + +##### column + +[`AppDisplayColumnDef`](AppDisplayColumnDef.md)\<`TFeatures`, `TData`, `TCellComponents`, `THeaderComponents`\> + +#### Returns + +`DisplayColumnDef`\<`TFeatures`, `TData`, `unknown`\> + +*** + +### group() + +```ts +group: (column) => GroupColumnDef; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:171](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L171) + +#### Parameters + +##### column + +[`AppGroupColumnDef`](AppGroupColumnDef.md)\<`TFeatures`, `TData`, `TCellComponents`, `THeaderComponents`\> + +#### Returns + +`GroupColumnDef`\<`TFeatures`, `TData`, `unknown`\> diff --git a/docs/framework/vue/reference/type-aliases/AppDisplayColumnDef.md b/docs/framework/vue/reference/type-aliases/AppDisplayColumnDef.md new file mode 100644 index 0000000000..ef062c1e99 --- /dev/null +++ b/docs/framework/vue/reference/type-aliases/AppDisplayColumnDef.md @@ -0,0 +1,50 @@ +--- +id: AppDisplayColumnDef +title: AppDisplayColumnDef +--- + +# Type Alias: AppDisplayColumnDef\ + +```ts +type AppDisplayColumnDef = Omit, "cell" | "header" | "footer"> & object; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:86](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L86) + +## Type Declaration + +### cell? + +```ts +optional cell: AppColumnDefTemplate>; +``` + +### footer? + +```ts +optional footer: AppColumnDefTemplate>; +``` + +### header? + +```ts +optional header: AppColumnDefTemplate>; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> diff --git a/docs/framework/vue/reference/type-aliases/AppGroupColumnDef.md b/docs/framework/vue/reference/type-aliases/AppGroupColumnDef.md new file mode 100644 index 0000000000..28beaab85f --- /dev/null +++ b/docs/framework/vue/reference/type-aliases/AppGroupColumnDef.md @@ -0,0 +1,56 @@ +--- +id: AppGroupColumnDef +title: AppGroupColumnDef +--- + +# Type Alias: AppGroupColumnDef\ + +```ts +type AppGroupColumnDef = Omit, "cell" | "header" | "footer" | "columns"> & object; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:106](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L106) + +## Type Declaration + +### cell? + +```ts +optional cell: AppColumnDefTemplate>; +``` + +### columns? + +```ts +optional columns: ColumnDef[]; +``` + +### footer? + +```ts +optional footer: AppColumnDefTemplate>; +``` + +### header? + +```ts +optional header: AppColumnDefTemplate>; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> diff --git a/docs/framework/vue/reference/type-aliases/AppHeaderContext.md b/docs/framework/vue/reference/type-aliases/AppHeaderContext.md new file mode 100644 index 0000000000..82964f0405 --- /dev/null +++ b/docs/framework/vue/reference/type-aliases/AppHeaderContext.md @@ -0,0 +1,68 @@ +--- +id: AppHeaderContext +title: AppHeaderContext +--- + +# Type Alias: AppHeaderContext\ + +```ts +type AppHeaderContext = object; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:49](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L49) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TValue + +`TValue` *extends* `CellData` + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +## Properties + +### column + +```ts +column: Column; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:55](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L55) + +*** + +### header + +```ts +header: Header & THeaderComponents & object; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:56](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L56) + +#### Type Declaration + +##### FlexRender + +```ts +FlexRender: Component; +``` + +*** + +### table + +```ts +table: Table; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:58](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L58) diff --git a/docs/framework/vue/reference/type-aliases/AppVueTable.md b/docs/framework/vue/reference/type-aliases/AppVueTable.md new file mode 100644 index 0000000000..12a3144a78 --- /dev/null +++ b/docs/framework/vue/reference/type-aliases/AppVueTable.md @@ -0,0 +1,70 @@ +--- +id: AppVueTable +title: AppVueTable +--- + +# Type Alias: AppVueTable\ + +```ts +type AppVueTable = VueTable & NoInfer & object; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:222](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L222) + +## Type Declaration + +### AppCell + +```ts +AppCell: Component>; +``` + +### AppFooter + +```ts +AppFooter: Component>; +``` + +### AppHeader + +```ts +AppHeader: Component>; +``` + +### AppTable + +```ts +AppTable: Component>; +``` + +### FlexRender + +```ts +FlexRender: typeof AppFlexRender; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TSelected + +`TSelected` + +### TTableComponents + +`TTableComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> diff --git a/docs/framework/vue/reference/type-aliases/ComponentType.md b/docs/framework/vue/reference/type-aliases/ComponentType.md new file mode 100644 index 0000000000..531ca19121 --- /dev/null +++ b/docs/framework/vue/reference/type-aliases/ComponentType.md @@ -0,0 +1,18 @@ +--- +id: ComponentType +title: ComponentType +--- + +# Type Alias: ComponentType\ + +```ts +type ComponentType = Component; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:32](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L32) + +## Type Parameters + +### T + +`T` *extends* `Record`\<`string`, `any`\> diff --git a/docs/framework/vue/reference/type-aliases/CreateTableHookOptions.md b/docs/framework/vue/reference/type-aliases/CreateTableHookOptions.md new file mode 100644 index 0000000000..a6bf85fb50 --- /dev/null +++ b/docs/framework/vue/reference/type-aliases/CreateTableHookOptions.md @@ -0,0 +1,50 @@ +--- +id: CreateTableHookOptions +title: CreateTableHookOptions +--- + +# Type Alias: CreateTableHookOptions\ + +```ts +type CreateTableHookOptions = Omit, "columns" | "data" | "store" | "state" | "initialState"> & object; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:181](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L181) + +## Type Declaration + +### cellComponents? + +```ts +optional cellComponents: TCellComponents; +``` + +### headerComponents? + +```ts +optional headerComponents: THeaderComponents; +``` + +### tableComponents? + +```ts +optional tableComponents: TTableComponents; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TTableComponents + +`TTableComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### TCellComponents + +`TCellComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> + +### THeaderComponents + +`THeaderComponents` *extends* `Record`\<`string`, [`ComponentType`](ComponentType.md)\<`any`\>\> diff --git a/docs/framework/vue/reference/type-aliases/SubscribeSource.md b/docs/framework/vue/reference/type-aliases/SubscribeSource.md new file mode 100644 index 0000000000..ab765e22ea --- /dev/null +++ b/docs/framework/vue/reference/type-aliases/SubscribeSource.md @@ -0,0 +1,22 @@ +--- +id: SubscribeSource +title: SubscribeSource +--- + +# Type Alias: SubscribeSource\ + +```ts +type SubscribeSource = + | Atom + | ReadonlyAtom + | Store +| ReadonlyStore; +``` + +Defined in: [packages/vue-table/src/useTable.ts:22](https://github.com/TanStack/table/blob/main/packages/vue-table/src/useTable.ts#L22) + +## Type Parameters + +### TValue + +`TValue` diff --git a/docs/framework/vue/reference/type-aliases/TableOptionsWithReactiveData.md b/docs/framework/vue/reference/type-aliases/TableOptionsWithReactiveData.md new file mode 100644 index 0000000000..3c9ac65696 --- /dev/null +++ b/docs/framework/vue/reference/type-aliases/TableOptionsWithReactiveData.md @@ -0,0 +1,22 @@ +--- +id: TableOptionsWithReactiveData +title: TableOptionsWithReactiveData +--- + +# Type Alias: TableOptionsWithReactiveData\ + +```ts +type TableOptionsWithReactiveData = { [K in keyof TableOptions]: K extends "data" ? MaybeRef> : MaybeRef[K]> }; +``` + +Defined in: [packages/vue-table/src/useTable.ts:28](https://github.com/TanStack/table/blob/main/packages/vue-table/src/useTable.ts#L28) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` diff --git a/docs/framework/vue/reference/type-aliases/VueTable.md b/docs/framework/vue/reference/type-aliases/VueTable.md new file mode 100644 index 0000000000..f7ebbdc8a2 --- /dev/null +++ b/docs/framework/vue/reference/type-aliases/VueTable.md @@ -0,0 +1,205 @@ +--- +id: VueTable +title: VueTable +--- + +# Type Alias: VueTable\ + +```ts +type VueTable = Table & object; +``` + +Defined in: [packages/vue-table/src/useTable.ts:61](https://github.com/TanStack/table/blob/main/packages/vue-table/src/useTable.ts#L61) + +## Type Declaration + +### state + +```ts +readonly state: Readonly; +``` + +The selected state of the table. This state may not match the structure of `table.store.state` because it is selected by the `selector` function that you pass as the 2nd argument to `useTable`. + +#### Example + +```ts +const table = useTable(options, (state) => ({ globalFilter: state.globalFilter })) // only globalFilter is part of the selected state + +console.log(table.state.globalFilter) +``` + +### Subscribe() + +```ts +Subscribe: { + (props): + | VNode + | VNode[]; + (props): + | VNode + | VNode[]; + (props): + | VNode + | VNode[]; +}; +``` + +Store mode: `selector` required. Source mode: pass `source` (atom or store); omit +`selector` for the whole value (identity), or pass `selector` to project. Split +overloads so source-only infers `TSourceValue` for `children` (see React `Subscribe`). + +#### Call Signature + +```ts +(props): + | VNode + | VNode[]; +``` + +##### Type Parameters + +###### TSourceValue + +`TSourceValue` + +##### Parameters + +###### props + +###### children + +(`state`) => `VNode` \| `VNode`[] \| `VNode` \| `VNode`[] + +###### selector? + +`undefined` + +###### source + +[`SubscribeSource`](SubscribeSource.md)\<`TSourceValue`\> + +##### Returns + + \| `VNode`\<`RendererNode`, `RendererElement`, \{ +\[`key`: `string`\]: `any`; +\}\> + \| `VNode`\<`RendererNode`, `RendererElement`, \{ +\[`key`: `string`\]: `any`; +\}\>[] + +#### Call Signature + +```ts +(props): + | VNode + | VNode[]; +``` + +##### Type Parameters + +###### TSourceValue + +`TSourceValue` + +###### TSubSelected + +`TSubSelected` + +##### Parameters + +###### props + +###### children + +(`state`) => `VNode` \| `VNode`[] \| `VNode` \| `VNode`[] + +###### selector + +(`state`) => `TSubSelected` + +###### source + +[`SubscribeSource`](SubscribeSource.md)\<`TSourceValue`\> + +##### Returns + + \| `VNode`\<`RendererNode`, `RendererElement`, \{ +\[`key`: `string`\]: `any`; +\}\> + \| `VNode`\<`RendererNode`, `RendererElement`, \{ +\[`key`: `string`\]: `any`; +\}\>[] + +#### Call Signature + +```ts +(props): + | VNode + | VNode[]; +``` + +##### Type Parameters + +###### TSubSelected + +`TSubSelected` + +##### Parameters + +###### props + +###### children + +(`state`) => `VNode` \| `VNode`[] \| `VNode` \| `VNode`[] + +###### selector + +(`state`) => `TSubSelected` + +##### Returns + + \| `VNode`\<`RendererNode`, `RendererElement`, \{ +\[`key`: `string`\]: `any`; +\}\> + \| `VNode`\<`RendererNode`, `RendererElement`, \{ +\[`key`: `string`\]: `any`; +\}\>[] + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* `TableFeatures` + +### TData + +`TData` *extends* `RowData` + +### TSelected + +`TSelected` = `TableState`\<`TFeatures`\> diff --git a/docs/framework/vue/reference/variables/AppFlexRender.md b/docs/framework/vue/reference/variables/AppFlexRender.md new file mode 100644 index 0000000000..079ca0d912 --- /dev/null +++ b/docs/framework/vue/reference/variables/AppFlexRender.md @@ -0,0 +1,55 @@ +--- +id: AppFlexRender +title: AppFlexRender +--- + +# Variable: AppFlexRender + +```ts +const AppFlexRender: DefineComponent; + }; + footer: { + default: undefined; + type: PropType; + }; + header: { + default: undefined; + type: PropType; + }; +}>, () => + | VNode + | null, { +}, { +}, { +}, ComponentOptionsMixin, ComponentOptionsMixin, { +}, string, PublicProps, ToResolvedProps; + }; + footer: { + default: undefined; + type: PropType; + }; + header: { + default: undefined; + type: PropType; + }; +}>, { +}>, { + cell: any; + footer: any; + header: any; +}, { +}, { +}, { +}, string, ComponentProvideOptions, true, { +}, any>; +``` + +Defined in: [packages/vue-table/src/createTableHook.ts:238](https://github.com/TanStack/table/blob/main/packages/vue-table/src/createTableHook.ts#L238) diff --git a/docs/framework/vue/reference/variables/FlexRender.md b/docs/framework/vue/reference/variables/FlexRender.md new file mode 100644 index 0000000000..e3c33b6d49 --- /dev/null +++ b/docs/framework/vue/reference/variables/FlexRender.md @@ -0,0 +1,55 @@ +--- +id: FlexRender +title: FlexRender +--- + +# Variable: FlexRender + +```ts +const FlexRender: DefineComponent<{ + cell?: any; + footer?: any; + header?: any; + props?: any; + render?: any; +}, () => any, { +}, { +}, { +}, ComponentOptionsMixin, ComponentOptionsMixin, { +}, string, PublicProps, ToResolvedProps<{ + cell?: any; + footer?: any; + header?: any; + props?: any; + render?: any; +}, { +}>, { + cell: any; + footer: any; + header: any; + props: any; + render: any; +}, { +}, { +}, { +}, string, ComponentProvideOptions, true, { +}, any>; +``` + +Defined in: [packages/vue-table/src/FlexRender.ts:52](https://github.com/TanStack/table/blob/main/packages/vue-table/src/FlexRender.ts#L52) + +Simplified component for rendering headers, cells, or footers. + +Supports both the new shorthand pattern and the legacy `:render`/`:props` pattern: + +## Example + +```vue + + + + + + + +``` diff --git a/docs/framework/vue/vue-table.md b/docs/framework/vue/vue-table.md index dc1d3d2a90..ee9b7ab5a1 100644 --- a/docs/framework/vue/vue-table.md +++ b/docs/framework/vue/vue-table.md @@ -2,43 +2,149 @@ title: Vue Table --- -The `@tanstack/vue-table` adapter is a wrapper around the core table logic. Most of it's job is related to managing state the "vue" way, providing types and the rendering implementation of cell/header/footer templates. +The `@tanstack/vue-table` adapter wraps `@tanstack/table-core` with Vue-specific reactivity, rendering helpers, and types. It installs the Vue `coreReativityFeature` for you, so table atoms can participate in Vue refs, computed values, and watchers. -## Exports +TanStack Table v9 is explicit about what a table uses. Register features with `_features`, and register client-side row model factories with `_rowModels`. The core row model is included by default, so a basic table can use `_rowModels: {}`. -`@tanstack/vue-table` re-exports all of `@tanstack/table-core`'s APIs and the following: +## Creating a Table -### `useVueTable` +Use `useTable` to create a Vue table instance. Table options can include Vue refs or computed values, including reactive `data`. -Takes an `options` object and returns a table. +```ts +import { ref } from 'vue' +import { tableFeatures, useTable, type ColumnDef } from '@tanstack/vue-table' + +type Person = { + firstName: string + lastName: string + age: number +} + +const _features = tableFeatures({}) + +const data = ref([]) + +const columns: Array> = [ + { + accessorKey: 'firstName', + header: 'First name', + cell: (info) => info.getValue(), + }, +] + +const table = useTable({ + _features, + _rowModels: {}, + columns, + data, +}) +``` + +For feature-specific row models, register the feature and put the row model factory under `_rowModels`. + +```ts +import { + createPaginatedRowModel, + createSortedRowModel, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/vue-table' + +const _features = tableFeatures({ + rowPaginationFeature, + rowSortingFeature, +}) + +const tableOptions = { + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, +} +``` + +## Table State + +Table state is managed with TanStack Store atoms in v9. For most tables, you do not need to manage table state yourself: set `initialState` when you need starting values, and use feature APIs like `table.nextPage()`, `table.setSorting(...)`, and `row.toggleSelected()` instead of mutating state directly. + +Use `atoms` when your app should own one state slice with TanStack Store. Use `state` with the matching `on[State]Change` option for simple Vue state integration or migration paths. Selected table state is available on `table.state` when you pass a selector to `useTable`. ```ts -import { useVueTable } from '@tanstack/vue-table' +import { createAtom } from '@tanstack/vue-store' +import { + rowPaginationFeature, + tableFeatures, + useTable, + type PaginationState, +} from '@tanstack/vue-table' -const table = useVueTable(options) -// ...render your table +const _features = tableFeatures({ + rowPaginationFeature, +}) +const paginationAtom = createAtom({ + pageIndex: 0, + pageSize: 10, +}) + +const table = useTable({ + _features, + _rowModels: {}, + columns, + data, + atoms: { + pagination: paginationAtom, + }, +}) ``` -### `FlexRender` +See the [Table State Guide](./guide/table-state.md) for selectors, external atoms, and state ownership patterns. -A Vue component for rendering cell/header/footer templates with dynamic values. +## Rendering Headers, Cells, and Footers -Example: +Use `FlexRender` to render column `header`, `cell`, and `footer` definitions. It handles plain values and Vue components. ```vue + ``` + +## createTableHook + +`createTableHook` creates an app-specific table hook. Use it when multiple tables should share `_features`, `_rowModels`, default options, column helpers, and component conventions. + +```ts +import { createTableHook, tableFeatures } from '@tanstack/vue-table' + +const { useAppTable, createAppColumnHelper } = createTableHook({ + _features: tableFeatures({}), + _rowModels: {}, +}) + +const columnHelper = createAppColumnHelper() + +const table = useAppTable({ + columns, + data, +}) +``` + +See the [Composable Tables example](./examples/composable-tables) for the full pattern. + +## API Reference + +See the [Vue API Reference](./reference/index.md). diff --git a/docs/guide/cells.md b/docs/guide/cells.md index 489411122e..ceaa470180 100644 --- a/docs/guide/cells.md +++ b/docs/guide/cells.md @@ -2,17 +2,13 @@ title: Cells Guide --- -## API - -[Cell API](../../api/core/cell) - ## Cells Guide This quick guide will discuss the different ways you can retrieve and interact with `cell` objects in TanStack Table. ### Where to Get Cells From -Cells come from [Rows](../rows). Enough said, right? +Cells come from [Rows](./rows). Enough said, right? There are multiple `row` instance APIs you can use to retrieve the appropriate cells from a row depending on which features you are using. Most commonly, you will use the `row.getAllCells` or `row.getVisibleCells` APIs (if you are using column visibility features), but there are a handful of other similar APIs that you can use. @@ -32,7 +28,7 @@ During grouping or aggregation features, the `cell.id` will have additional stri #### Cell Parent Objects -Every cell stores a reference to its parent [row](../rows) and [column](../columns) objects. +Every cell stores a reference to its parent [row](./rows) and [column](./columns) objects. #### Access Cell Values @@ -82,4 +78,4 @@ const columns = [ {row.getVisibleCells().map(cell => { return {flexRender(cell.column.columnDef.cell, cell.getContext())} })} - \ No newline at end of file + diff --git a/docs/guide/column-defs.md b/docs/guide/column-defs.md index 4b4f997dc9..d4425d852b 100644 --- a/docs/guide/column-defs.md +++ b/docs/guide/column-defs.md @@ -2,19 +2,15 @@ title: Columns Definitions Guide --- -## API - -[Column Def](../../api/core/column-def) - ## Column Definitions Guide -> Note: This guide is about setting up column definitions for your table and NOT about the actual [`column`](../columns) objects that are generated within the table instance. +> Note: This guide is about setting up column definitions for your table and NOT about the actual [`column`](./columns) objects that are generated within the table instance. Column defs are the single most important part of building a table. They are responsible for: - Building the underlying data model that will be used for everything including sorting, filtering, grouping, etc. - Formatting the data model into what will be displayed in the table -- Creating [header groups](../../../api/core/header-group), [headers](../../../api/core/header) and [footers](../../../api/core/column-def#footer) +- Creating [header groups](../reference/index/interfaces/HeaderGroup_Core), [headers](../reference/index/interfaces/Header_Core) and footers - Creating columns for display-only purposes, eg. action buttons, checkboxes, expanders, sparklines, etc. ## Column Def Types @@ -30,7 +26,9 @@ The following "types" of column defs aren't actually TypeScript types, but more ## Column Helpers -While column defs are just plain objects at the end of the day, a `createColumnHelper` function is exposed from the table core which, when called with a row type, returns a utility for creating different column definition types with the highest type-safety possible. +While column defs are just plain objects at the end of the day, a `createColumnHelper` function is exposed from the table core which, when called with your features type and row type, returns a utility for creating different column definition types with the highest type-safety possible. + +In v9, `createColumnHelper` requires two type parameters: `TFeatures` (from your `_features` object) and `TData` (your row type). Use `typeof _features` to get the features type. Here's an example of creating and using a column helper: @@ -45,10 +43,11 @@ type Person = { progress: number } -const columnHelper = createColumnHelper() +const _features = tableFeatures({}) // or tableFeatures({ rowSortingFeature, ... }) +const columnHelper = createColumnHelper() -// Make some columns! -const defaultColumns = [ +// Make some columns! Use columnHelper.columns([...]) for better type inference with nested groups +const defaultColumns = columnHelper.columns([ // Display Column columnHelper.display({ id: 'actions', @@ -106,7 +105,7 @@ const defaultColumns = [ }), ], }), -] +]) ``` ## Creating Accessor Columns @@ -276,7 +275,7 @@ columnHelper.accessor('firstName', { ## Aggregated Cell Formatting -For more info on aggregated cells, see [grouping](../grouping). +For more info on aggregated cells, see [grouping](./grouping). ## Header & Footer Formatting diff --git a/docs/guide/column-faceting.md b/docs/guide/column-faceting.md index 3931bb9e89..883ee9008d 100644 --- a/docs/guide/column-faceting.md +++ b/docs/guide/column-faceting.md @@ -6,11 +6,33 @@ title: Column Faceting Guide Want to skip to the implementation? Check out these examples: -- [filters-faceted](../../framework/react/examples/filters-faceted) + -## API +# React -[Column Faceting API](../../api/features/column-faceting) +- [Faceted Filters](../framework/react/examples/filters-faceted) + +# Preact + +- [Faceted Filters](../framework/preact/examples/filters-faceted) + +# Solid + +- [Faceted Filters](../framework/solid/examples/filters-faceted) + +# Svelte + +- [Faceted Filters](../framework/svelte/examples/filters-faceted) + +# Vue + +- [Faceted Filters](../framework/vue/examples/filters-faceted) + +# Lit + +- [Faceted Filters](../framework/lit/examples/filters-faceted) + + ## Column Faceting Guide @@ -18,29 +40,33 @@ Column Faceting is a feature that allows you to generate lists of values for a g ### Column Faceting Row Models -In order to use any of the column faceting features, you must include the appropriate row models in your table options. +In order to use any of the column faceting features, add the `columnFacetingFeature` to your features and the appropriate faceted row models to `_rowModels`. ```ts -//only import the row models you need import { - getCoreRowModel, - getFacetedRowModel, - getFacetedMinMaxValues, //depends on getFacetedRowModel - getFacetedUniqueValues, //depends on getFacetedRowModel -} -//... -const table = useReactTable({ + useTable, + tableFeatures, + columnFacetingFeature, + createFacetedRowModel, + createFacetedMinMaxValues, + createFacetedUniqueValues, +} from '@tanstack/react-table' + +const _features = tableFeatures({ columnFacetingFeature }) + +const table = useTable({ + _features, + _rowModels: { + facetedRowModel: createFacetedRowModel(), // required for faceting (other faceted row models depend on this) + facetedMinMaxValues: createFacetedMinMaxValues(), // if you need min/max values + facetedUniqueValues: createFacetedUniqueValues(), // if you need a list of unique values + }, columns, data, - getCoreRowModel: getCoreRowModel(), - getFacetedRowModel: getFacetedRowModel(), //if you need a list of values for a column (other faceted row models depend on this one) - getFacetedMinMaxValues: getFacetedMinMaxValues(), //if you need min/max values - getFacetedUniqueValues: getFacetedUniqueValues(), //if you need a list of unique values - //... }) ``` -First, you must include the `getFacetedRowModel` row model. This row model will generate a list of values for a given column. If you need a list of unique values, include the `getFacetedUniqueValues` row model. If you need a tuple of minimum and maximum values, include the `getFacetedMinMaxValues` row model. +First, you must include the `facetedRowModel`. This row model will generate a list of values for a given column. If you need a list of unique values, include the `facetedUniqueValues` row model. If you need a tuple of minimum and maximum values, include the `facetedMinMaxValues` row model. ### Use Faceted Row Models @@ -68,22 +94,26 @@ const facetingQuery = useQuery( //... ) -const table = useReactTable({ +const table = useTable({ + _features, + _rowModels: { + facetedRowModel: createFacetedRowModel(), + facetedUniqueValues: createFacetedUniqueValues(), + facetedMinMaxValues: createFacetedMinMaxValues(), + }, columns, data, - getCoreRowModel: getCoreRowModel(), - getFacetedRowModel: getFacetedRowModel(), getFacetedUniqueValues: (table, columnId) => { - const uniqueValueMap = new Map(); + const uniqueValueMap = new Map() //... - return uniqueValueMap; + return uniqueValueMap }, getFacetedMinMaxValues: (table, columnId) => { //... - return [min, max]; + return [min, max] }, //... }) ``` -Alternatively, you don't have to put any of your faceting logic through the TanStack Table APIs at all. Just fetch your lists and pass them to your filter components directly. \ No newline at end of file +Alternatively, you don't have to put any of your faceting logic through the TanStack Table APIs at all. Just fetch your lists and pass them to your filter components directly. diff --git a/docs/guide/column-filtering.md b/docs/guide/column-filtering.md index f6eb4e68bc..1e1cde98fc 100644 --- a/docs/guide/column-filtering.md +++ b/docs/guide/column-filtering.md @@ -6,18 +6,49 @@ title: Column Filtering Guide Want to skip to the implementation? Check out these examples: -- [Column Filters](../../framework/react/examples/filters) -- [Faceted Filters](../../framework/react/examples/filters-faceted) (Autocomplete and Range filters) -- [Fuzzy Search](../../framework/react/examples/filters-fuzzy) (Match Sorter) -- [Editable Data](../../framework/react/examples/editable-data) -- [Expanding](../../framework/react/examples/expanding) (Filtering from Sub-Rows) -- [Grouping](../../framework/react/examples/grouping) -- [Pagination](../../framework/react/examples/pagination) -- [Row Selection](../../framework/react/examples/row-selection) + -## API +# React -[Column Filtering API](../../api/features/column-filtering) +- [Column Filters](../framework/react/examples/filters) +- [Faceted Filters](../framework/react/examples/filters-faceted) +- [Fuzzy Search](../framework/react/examples/filters-fuzzy) + +# Preact + +- [Column Filters](../framework/preact/examples/filters) +- [Faceted Filters](../framework/preact/examples/filters-faceted) +- [Fuzzy Search](../framework/preact/examples/filters-fuzzy) + +# Solid + +- [Column Filters](../framework/solid/examples/filters) +- [Faceted Filters](../framework/solid/examples/filters-faceted) +- [Fuzzy Search](../framework/solid/examples/filters-fuzzy) + +# Svelte + +- [Column Filters](../framework/svelte/examples/filtering) +- [Faceted Filters](../framework/svelte/examples/filters-faceted) +- [Fuzzy Search](../framework/svelte/examples/filters-fuzzy) + +# Vue + +- [Column Filters](../framework/vue/examples/filters) +- [Faceted Filters](../framework/vue/examples/filters-faceted) +- [Fuzzy Search](../framework/vue/examples/filters-fuzzy) + +# Angular + +- [Column Filters](../framework/angular/examples/filters) + +# Lit + +- [Column Filters](../framework/lit/examples/filters) +- [Faceted Filters](../framework/lit/examples/filters-faceted) +- [Fuzzy Search](../framework/lit/examples/filters-fuzzy) + + ## Column Filtering Guide @@ -31,7 +62,7 @@ TanStack table supports both client-side and manual server-side filtering. This If you have a large dataset, you may not want to load all of that data into the client's browser in order to filter it. In this case, you will most likely want to implement server-side filtering, sorting, pagination, etc. -However, as also discussed in the [Pagination Guide](../pagination#should-you-use-client-side-pagination), a lot of developers underestimate how many rows can be loaded client-side without a performance hit. The TanStack table examples are often tested to handle up to 100,000 rows or more with decent performance for client-side filtering, sorting, pagination, and grouping. This doesn't necessarily mean that your app will be able to handle that many rows, but if your table is only going to have a few thousand rows at most, you might be able to take advantage of the client-side filtering, sorting, pagination, and grouping that TanStack table provides. +However, as also discussed in the [Pagination Guide](./pagination#should-you-use-client-side-pagination), a lot of developers underestimate how many rows can be loaded client-side without a performance hit. The TanStack table examples are often tested to handle up to 100,000 rows or more with decent performance for client-side filtering, sorting, pagination, and grouping. This doesn't necessarily mean that your app will be able to handle that many rows, but if your table is only going to have a few thousand rows at most, you might be able to take advantage of the client-side filtering, sorting, pagination, and grouping that TanStack table provides. > TanStack Table can handle thousands of client-side rows with good performance. Don't rule out client-side filtering, pagination, sorting, etc. without some thought first. @@ -47,14 +78,14 @@ If you're not sure, you can always start with client-side filtering and paginati If you have decided that you need to implement server-side filtering instead of using the built-in client-side filtering, here's how you do that. -No `getFilteredRowModel` table option is needed for manual server-side filtering. Instead, the `data` that you pass to the table should already be filtered. However, if you have passed a `getFilteredRowModel` table option, you can tell the table to skip it by setting the `manualFiltering` option to `true`. +No `filteredRowModel` is needed for manual server-side filtering. Instead, the `data` that you pass to the table should already be filtered. However, if you have added a `filteredRowModel` to `_rowModels`, you can tell the table to skip it by setting the `manualFiltering` option to `true`. ```jsx -const table = useReactTable({ +const table = useTable({ + _features: tableFeatures({ columnFilteringFeature }), + _rowModels: {}, // no filteredRowModel needed for manual server-side filtering data, columns, - getCoreRowModel: getCoreRowModel(), - // getFilteredRowModel: getFilteredRowModel(), // not needed for manual server-side filtering manualFiltering: true, }) ``` @@ -63,16 +94,26 @@ const table = useReactTable({ ### Client-Side Filtering -If you are using the built-in client-side filtering features, first you need to pass in a `getFilteredRowModel` function to the table options. This function will be called whenever the table needs to filter the data. You can either import the default `getFilteredRowModel` function from TanStack Table or create your own. +If you are using the built-in client-side filtering features, add the `columnFilteringFeature` to your features and the `filteredRowModel` to your row models. Import `createFilteredRowModel` and `filterFns` from TanStack Table: ```jsx -import { useReactTable, getFilteredRowModel } from '@tanstack/react-table' -//... -const table = useReactTable({ +import { + useTable, + tableFeatures, + columnFilteringFeature, + createFilteredRowModel, + filterFns, +} from '@tanstack/react-table' + +const _features = tableFeatures({ columnFilteringFeature }) + +const table = useTable({ + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + }, data, columns, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), // needed for client-side filtering }) ``` @@ -94,16 +135,18 @@ Since the column filter state is an array of objects, you can have multiple colu #### Accessing Column Filter State -You can access the column filter state from the table instance just like any other table state using the `table.getState()` API. +You can access the column filter state from the table instance with `table.atoms.columnFilters.get()` or from the current `table.store.state` snapshot. ```jsx -const table = useReactTable({ +const table = useTable({ + _features, + _rowModels: { filteredRowModel: createFilteredRowModel(filterFns) }, columns, data, //... }) -console.log(table.getState().columnFilters) // access the column filters state from the table instance +console.log(table.atoms.columnFilters.get()) // access the current column filters state ``` However, if you need to access the column filter state before the table is initialized, you can "control" the column filter state like down below. @@ -115,7 +158,9 @@ If you need easy access to the column filter state, you can control/manage the c ```tsx const [columnFilters, setColumnFilters] = useState([]) // can set initial column filter state here //... -const table = useReactTable({ +const table = useTable({ + _features, + _rowModels: { filteredRowModel: createFilteredRowModel(filterFns) }, columns, data, //... @@ -131,7 +176,9 @@ const table = useReactTable({ If you do not need to control the column filter state in your own state management or scope, but you still want to set an initial column filter state, you can use the `initialState` table option instead of `state`. ```jsx -const table = useReactTable({ +const table = useTable({ + _features, + _rowModels: { filteredRowModel: createFilteredRowModel(filterFns) }, columns, data, //... @@ -146,7 +193,7 @@ const table = useReactTable({ }) ``` -> **NOTE**: Do not use both `initialState.columnFilters` and `state.columnFilters` at the same time, as the initialized state in the `state.columnFilters` will override the `initialState.columnFilters`. +> **NOTE**: Do not use both `initialState.columnFilters` and `state.columnFilters` at the same time, as the controlled `state.columnFilters` value will override the `initialState.columnFilters`. ### FilterFns @@ -212,17 +259,19 @@ const columns = [ } ] //... -const table = useReactTable({ +const table = useTable({ + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel({ + ...filterFns, + myCustomFilterFn: (row, columnId, filterValue) => { + return // true or false based on your custom logic + }, + startsWith: startsWithFilterFn, // defined elsewhere + }), + }, columns, data, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - filterFns: { // add a custom global filter function - myCustomFilterFn: (row, columnId, filterValue) => { // defined inline here - return // true or false based on your custom logic - }, - startsWith: startsWithFilterFn, // defined elsewhere - }, }) ``` @@ -236,7 +285,7 @@ You can attach a few other properties to filter functions to customize their beh ```tsx const startsWithFilterFn = ( - row: Row, + row: Row, columnId: string, filterValue: number | string, //resolveFilterValue will transform this to a string ) => @@ -274,7 +323,9 @@ const columns = [ //... ] //... -const table = useReactTable({ +const table = useTable({ + _features, + _rowModels: { filteredRowModel: createFilteredRowModel(filterFns) }, columns, data, enableColumnFilters: false, // disable column filtering for all columns @@ -292,12 +343,14 @@ By default, filtering is done from parent rows down, so if a parent row is filte However, if you want to allow sub-rows to be filtered and searched through, regardless of whether the parent row is filtered out, you can set the `filterFromLeafRows` table option to `true`. Setting this option to `true` will cause filtering to be done from leaf rows up, which means parent rows will be included so long as one of their child or grand-child rows is also included. ```jsx -const table = useReactTable({ +const table = useTable({ + _features: tableFeatures({ columnFilteringFeature, rowExpandingFeature }), + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + expandedRowModel: createExpandedRowModel(), + }, columns, data, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getExpandedRowModel: getExpandedRowModel(), filterFromLeafRows: true, // filter and search through sub-rows }) ``` @@ -309,12 +362,14 @@ By default, filtering is done for all rows in a tree, no matter if they are root Use `maxLeafRowFilterDepth: 0` if you want to preserve a parent row's sub-rows from being filtered out while the parent row is passing the filter. ```jsx -const table = useReactTable({ +const table = useTable({ + _features: tableFeatures({ columnFilteringFeature, rowExpandingFeature }), + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + expandedRowModel: createExpandedRowModel(), + }, columns, data, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getExpandedRowModel: getExpandedRowModel(), maxLeafRowFilterDepth: 0, // only filter root level parent rows out }) ``` diff --git a/docs/guide/column-ordering.md b/docs/guide/column-ordering.md index 42a7441da7..8f463ed675 100644 --- a/docs/guide/column-ordering.md +++ b/docs/guide/column-ordering.md @@ -6,12 +6,38 @@ title: Column Ordering Guide Want to skip to the implementation? Check out these examples: -- [column-ordering](../../framework/react/examples/column-ordering) -- [column-dnd](../../framework/react/examples/column-dnd) + -## API +# React -[Column Ordering API](../../api/features/column-ordering) +- [Column Ordering](../framework/react/examples/column-ordering) +- [Column DnD](../framework/react/examples/column-dnd) + +# Preact + +- [Column Ordering](../framework/preact/examples/column-ordering) + +# Solid + +- [Column Ordering](../framework/solid/examples/column-ordering) + +# Svelte + +- [Column Ordering](../framework/svelte/examples/column-ordering) + +# Vue + +- [Column Ordering](../framework/vue/examples/column-ordering) + +# Angular + +- [Column Ordering](../framework/angular/examples/column-ordering) + +# Lit + +- [Column Ordering](../framework/lit/examples/column-ordering) + + ## Column Ordering Guide @@ -21,9 +47,9 @@ By default, columns are ordered in the order they are defined in the `columns` a There are 3 table features that can reorder columns, which happen in the following order: -1. [Column Pinning](../column-pinning) - If pinning, columns are split into left, center (unpinned), and right pinned columns. +1. [Column Pinning](./column-pinning) - If pinning, columns are split into left, center (unpinned), and right pinned columns. 2. Manual **Column Ordering** - A manually specified column order is applied. -3. [Grouping](../grouping) - If grouping is enabled, a grouping state is active, and `tableOptions.groupedColumnMode` is set to `'reorder' | 'remove'`, then the grouped columns are reordered to the start of the column flow. +3. [Grouping](./grouping) - If grouping is enabled, a grouping state is active, and `tableOptions.groupedColumnMode` is set to `'reorder' | 'remove'`, then the grouped columns are reordered to the start of the column flow. > **Note:** `columnOrder` state will only affect unpinned columns if used in conjunction with column pinning. @@ -36,13 +62,15 @@ If you don't provide a `columnOrder` state, TanStack Table will just use the ord If all you need to do is specify the initial column order, you can just specify the `columnOrder` state in the `initialState` table option. ```jsx -const table = useReactTable({ +const table = useTable({ + _features: tableFeatures({ columnOrderingFeature }), + _rowModels: {}, //... initialState: { columnOrder: ['columnId1', 'columnId2', 'columnId3'], - } + }, //... -}); +}) ``` > **Note:** If you are using the `state` table option to also specify the `columnOrder` state, the `initialState` will have no effect. Only specify particular states in either `initialState` or `state`, not both. @@ -52,9 +80,11 @@ const table = useReactTable({ If you need to dynamically change the column order, or set the column order after the table has been initialized, you can manage the `columnOrder` state just like any other table state. ```jsx -const [columnOrder, setColumnOrder] = useState(['columnId1', 'columnId2', 'columnId3']); //optionally initialize the column order +const [columnOrder, setColumnOrder] = useState(['columnId1', 'columnId2', 'columnId3']) //... -const table = useReactTable({ +const table = useTable({ + _features: tableFeatures({ columnOrderingFeature }), + _rowModels: {}, //... state: { columnOrder, @@ -77,9 +107,9 @@ const [movingColumnId, setMovingColumnId] = useState(null); const [targetColumnId, setTargetColumnId] = useState(null); //util function to splice and reorder the columnOrder array -const reorderColumn = ( - movingColumnId: Column, - targetColumnId: Column, +const reorderColumn = ( + movingColumnId: Column, + targetColumnId: Column, ): string[] => { const newColumnOrder = [...columnOrder]; newColumnOrder.splice( @@ -104,8 +134,8 @@ There are undoubtedly many ways to implement drag and drop features along-side T 1. Do NOT try to use [`"react-dnd"`](https://react-dnd.github.io/react-dnd/docs/overview) _if you are using React 18 or newer_. React DnD was an important library for its time, but it now does not get updated very often, and it has incompatibilities with React 18, especially in React Strict Mode. It is still possible to get it to work, but there are newer alternatives that have better compatibility and are more actively maintained. React DnD's Provider may also interfere and conflict with any other DnD solutions you may want to try in your app. -2. Use [`"@dnd-kit/core"`](https://dndkit.com/). DnD Kit is a modern, modular and lightweight drag and drop library that is highly compatible with the modern React ecosystem, and it works well with semantic `` markup. Both of the official TanStack DnD examples, [Column DnD](../../framework/react/examples/column-dnd) and [Row DnD](../../framework/react/examples/row-dnd), now use DnD Kit. +2. Use [`"@dnd-kit/core"`](https://dndkit.com/). DnD Kit is a modern, modular and lightweight drag and drop library that is highly compatible with the modern React ecosystem, and it works well with semantic `
` markup. Both of the official TanStack DnD examples, [Column DnD](../framework/react/examples/column-dnd) and [Row DnD](../framework/react/examples/row-dnd), now use DnD Kit. 3. Consider other DnD libraries like [`"react-beautiful-dnd"`](https://github.com/atlassian/react-beautiful-dnd), but be aware of their potentially large bundle sizes, maintenance status, and compatibility with `
` markup. -4. Consider using native browser events and state management to implement lightweight drag and drop features. However, be aware that this approach may not be best for mobile users if you do not go the extra mile to implement proper touch events. [Material React Table V2](https://www.material-react-table.com/docs/examples/column-ordering) is an example of a library that implements TanStack Table with only browser drag and drop events such as `onDragStart`, `onDragEnd`, `onDragEnter` and no other dependencies. Browse its source code to see how it is done. \ No newline at end of file +4. Consider using native browser events and state management to implement lightweight drag and drop features. However, be aware that this approach may not be best for mobile users if you do not go the extra mile to implement proper touch events. [Material React Table V2](https://www.material-react-table.com/docs/examples/column-ordering) is an example of a library that implements TanStack Table with only browser drag and drop events such as `onDragStart`, `onDragEnd`, `onDragEnter` and no other dependencies. Browse its source code to see how it is done. diff --git a/docs/guide/column-pinning.md b/docs/guide/column-pinning.md index 5a201e741e..664a525c1c 100644 --- a/docs/guide/column-pinning.md +++ b/docs/guide/column-pinning.md @@ -6,17 +6,50 @@ title: Column Pinning Guide Want to skip to the implementation? Check out these examples: -- [column-pinning](../../framework/react/examples/column-pinning) -- [sticky-column-pinning](../../framework/react/examples/column-pinning-sticky) + - ### Other Examples - -- [Svelte column-pinning](../../framework/svelte/examples/column-pinning) -- [Vue column-pinning](../../framework/vue/examples/column-pinning) +# React -## API +- [Column Pinning](../framework/react/examples/column-pinning) +- [Column Pinning Split](../framework/react/examples/column-pinning-split) +- [Sticky Column Pinning](../framework/react/examples/column-pinning-sticky) -[Column Pinning API](../../api/features/column-pinning) +# Preact + +- [Column Pinning](../framework/preact/examples/column-pinning) +- [Column Pinning Split](../framework/preact/examples/column-pinning-split) +- [Sticky Column Pinning](../framework/preact/examples/column-pinning-sticky) + +# Solid + +- [Column Pinning](../framework/solid/examples/column-pinning) +- [Column Pinning Split](../framework/solid/examples/column-pinning-split) +- [Sticky Column Pinning](../framework/solid/examples/column-pinning-sticky) + +# Svelte + +- [Column Pinning](../framework/svelte/examples/column-pinning) +- [Column Pinning Split](../framework/svelte/examples/column-pinning-split) +- [Sticky Column Pinning](../framework/svelte/examples/column-pinning-sticky) + +# Vue + +- [Column Pinning](../framework/vue/examples/column-pinning) +- [Column Pinning Split](../framework/vue/examples/column-pinning-split) +- [Sticky Column Pinning](../framework/vue/examples/column-pinning-sticky) + +# Angular + +- [Column Pinning](../framework/angular/examples/column-pinning) +- [Sticky Column Pinning](../framework/angular/examples/column-pinning-sticky) + +# Lit + +- [Column Pinning](../framework/lit/examples/column-pinning) +- [Column Pinning Split](../framework/lit/examples/column-pinning-split) +- [Sticky Column Pinning](../framework/lit/examples/column-pinning-sticky) + + ## Column Pinning Guide @@ -27,8 +60,8 @@ TanStack Table offers state and APIs helpful for implementing column pinning fea There are 3 table features that can reorder columns, which happen in the following order: 1. **Column Pinning** - If pinning, columns are split into left, center (unpinned), and right pinned columns. -2. Manual [Column Ordering](../column-ordering) - A manually specified column order is applied. -3. [Grouping](../grouping) - If grouping is enabled, a grouping state is active, and `tableOptions.groupedColumnMode` is set to `'reorder' | 'remove'`, then the grouped columns are reordered to the start of the column flow. +2. Manual [Column Ordering](./column-ordering) - A manually specified column order is applied. +3. [Grouping](./grouping) - If grouping is enabled, a grouping state is active, and `tableOptions.groupedColumnMode` is set to `'reorder' | 'remove'`, then the grouped columns are reordered to the start of the column flow. The only way to change the order of the pinned columns is in the `columnPinning.left` and `columnPinning.right` state itself. `columnOrder` state will only affect the order of the unpinned ("center") columns. @@ -37,20 +70,26 @@ The only way to change the order of the pinned columns is in the `columnPinning. Managing the `columnPinning` state is optional, and usually not necessary unless you are adding persistent state features. TanStack Table will already keep track of the column pinning state for you. Manage the `columnPinning` state just like any other table state if you need to. ```jsx +import { useTable, tableFeatures, columnPinningFeature } from '@tanstack/react-table' + +const _features = tableFeatures({ columnPinningFeature }) + const [columnPinning, setColumnPinning] = useState({ left: [], right: [], -}); -//... -const table = useReactTable({ +}) + +const table = useTable({ + _features, + _rowModels: {}, //... state: { columnPinning, //... - } + }, onColumnPinningChange: setColumnPinning, //... -}); +}) ``` ### Pin Columns by Default @@ -58,7 +97,9 @@ const table = useReactTable({ A very common use case is to pin some columns by default. You can do this by either initializing the `columnPinning` state with the pinned columnIds, or by using the `initialState` table option ```jsx -const table = useReactTable({ +const table = useTable({ + _features, + _rowModels: {}, //... initialState: { columnPinning: { @@ -66,24 +107,24 @@ const table = useReactTable({ right: ['actions-column'], }, //... - } + }, //... -}); +}) ``` ### Useful Column Pinning APIs -> Note: Some of these APIs are new in v8.12.0 +> Note: These APIs are available when using `columnPinningFeature`. There are a handful of useful Column API methods to help you implement column pinning features: -- [`column.getCanPin`](../../api/features/column-pinning#getcanpin): Use to determine if a column can be pinned. -- [`column.pin`](../../api/features/column-pinning#pin): Use to pin a column to the left or right. Or use to unpin a column. -- [`column.getIsPinned`](../../api/features/column-pinning#getispinned): Use to determine where a column is pinned. -- [`column.getStart`](../../api/features/column-pinning#getstart): Use to provide the correct `left` CSS value for a pinned column. -- [`column.getAfter`](../../api/features/column-pinning#getafter): Use to provide the correct `right` CSS value for a pinned column. -- [`column.getIsLastColumn`](../../api/features/column-pinning#getislastcolumn): Use to determine if a column is the last column in its pinned group. Useful for adding a box-shadow -- [`column.getIsFirstColumn`](../../api/features/column-pinning#getisfirstcolumn): Use to determine if a column is the first column in its pinned group. Useful for adding a box-shadow +- `column.getCanPin`: Use to determine if a column can be pinned. +- `column.pin`: Use to pin a column to the left or right. Or use to unpin a column. +- `column.getIsPinned`: Use to determine where a column is pinned. +- `column.getStart`: Use to provide the correct `left` CSS value for a pinned column. +- `column.getAfter`: Use to provide the correct `right` CSS value for a pinned column. +- `column.getIsLastColumn`: Use to determine if a column is the last column in its pinned group. Useful for adding a box-shadow. +- `column.getIsFirstColumn`: Use to determine if a column is the first column in its pinned group. Useful for adding a box-shadow. ### Split Table Column Pinning diff --git a/docs/guide/column-resizing.md b/docs/guide/column-resizing.md new file mode 100644 index 0000000000..cf4b207ce4 --- /dev/null +++ b/docs/guide/column-resizing.md @@ -0,0 +1,178 @@ +--- +title: Column Resizing Guide +--- + +## Examples + +Want to skip to the implementation? Check out these examples: + + + +# React + +- [Column Resizing](../framework/react/examples/column-resizing) +- [Performant Column Resizing](../framework/react/examples/column-resizing-performant) + +# Preact + +- [Column Resizing](../framework/preact/examples/column-resizing) +- [Performant Column Resizing](../framework/preact/examples/column-resizing-performant) + +# Solid + +- [Column Resizing](../framework/solid/examples/column-resizing) +- [Performant Column Resizing](../framework/solid/examples/column-resizing-performant) + +# Svelte + +- [Column Resizing](../framework/svelte/examples/column-resizing) +- [Performant Column Resizing](../framework/svelte/examples/column-resizing-performant) + +# Vue + +- [Column Resizing](../framework/vue/examples/column-resizing) +- [Performant Column Resizing](../framework/vue/examples/column-resizing-performant) + +# Angular + +- [Performant Column Resizing](../framework/angular/examples/column-resizing-performant) + +# Lit + +- [Column Resizing](../framework/lit/examples/column-resizing) +- [Performant Column Resizing](../framework/lit/examples/column-resizing-performant) + + + +## Column Resizing Guide + +TanStack Table provides built-in column resizing state and APIs that allow you to easily implement column resizing in your table UI with a variety of options for UX and performance. + +Column resizing builds on column sizing. If you only need to define starting, minimum, or maximum widths, see the [Column Sizing Guide](./column-sizing). + +### Enable Column Resizing + +To use column resizing, add `columnResizingFeature` to your features. The `column.getCanResize()` API will return `true` by default for all columns, but you can either disable column resizing for all columns with the `enableColumnResizing` table option, or disable column resizing on a per-column basis with the `enableResizing` column option. + +```tsx +import { + columnResizingFeature, + columnSizingFeature, + tableFeatures, + useTable, +} from '@tanstack/react-table' + +const _features = tableFeatures({ + columnSizingFeature, + columnResizingFeature, +}) + +const columns = [ + { + accessorKey: 'id', + enableResizing: false, // disable resizing for just this column + size: 200, // starting column size + }, + //... +] + +const table = useTable({ + _features, + _rowModels: {}, + columns, + data, +}) +``` + +### Column Resize Mode + +By default, the column resize mode is set to `"onEnd"`. This means that the `column.getSize()` API will not return the new column size until the user has finished resizing (dragging) the column. Usually a small UI indicator will be displayed while the user is resizing the column. + +In the React TanStack Table adapter, where achieving 60 fps column resizing renders can be difficult depending on the complexity of your table or web page, the `"onEnd"` column resize mode can be a good default option to avoid stuttering or lagging while the user resizes columns. That is not to say that you cannot achieve 60 fps column resizing renders while using TanStack React Table, but you may have to do some extra memoization or other performance optimizations in order to achieve this. + +> Advanced column resizing performance tips will be discussed [down below](#advanced-column-resizing-performance). + +If you want to change the column resize mode to `"onChange"` for immediate column resizing renders, you can do so with the `columnResizeMode` table option. + +```tsx +const table = useTable({ + //... + columnResizeMode: 'onChange', // change column resize mode to "onChange" +}) +``` + +### Column Resize Direction + +By default, TanStack Table assumes that the table markup is laid out in a left-to-right direction. For right-to-left layouts, you may need to change the column resize direction to `"rtl"`. + +```tsx +const table = useTable({ + //... + columnResizeDirection: 'rtl', // change column resize direction to "rtl" for certain locales +}) +``` + +### Connect Column Resizing APIs to UI + +There are a few really handy APIs that you can use to hook up your column resizing drag interactions to your UI. + +#### Column Size APIs + +To apply the size of a column to the column head cells, data cells, or footer cells, you can use the following APIs: + +```ts +header.getSize() +column.getSize() +cell.column.getSize() +``` + +How you apply these size styles to your markup is up to you, but it is pretty common to use either CSS variables or inline styles to apply the column sizes. + +```tsx + ) ``` diff --git a/docs/guide/data.md b/docs/guide/data.md index 77f5d0f874..79081dbe20 100644 --- a/docs/guide/data.md +++ b/docs/guide/data.md @@ -133,7 +133,7 @@ const columns = [ ] ``` -This is discussed in more detail in the [Column Def Guide](../column-defs). +This is discussed in more detail in the [Column Def Guide](./column-defs). > NOTE: The "keys" in your json data can usually be anything, but any periods in the keys will be interpreted as a deep key and will cause errors. @@ -179,7 +179,7 @@ type User = { } ``` -Where `subRows` is an optional array of `User` objects. This is discussed in more detail in the [Expanding Guide](../expanding). +Where `subRows` is an optional array of `User` objects. This is discussed in more detail in the [Expanding Guide](./expanding). ### Give Data a "Stable" Reference @@ -189,6 +189,7 @@ This will depend on which framework adapter you are using, but in React, you sho ```tsx const fallbackData = [] +const _features = tableFeatures({}) // Define outside component for stable reference export default function MyComponent() { //✅ GOOD: This will not cause an infinite loop of re-renders because `columns` is a stable reference @@ -202,7 +203,9 @@ export default function MyComponent() { ]); // Columns and data are defined in a stable reference, will not cause infinite loop! - const table = useReactTable({ + const table = useTable({ + _features, + _rowModels: {}, columns, data ?? fallbackData, //also good to use a fallback array that is defined outside of the component (stable reference) }); @@ -213,7 +216,7 @@ export default function MyComponent() { `React.useState` and `React.useMemo` are not the only ways to give your data a stable reference. You can also define your data outside of the component or use a 3rd party state management library like Redux, Zustand, or TanStack Query. -The main thing to avoid is defining the `data` array inside the same scope as the `useReactTable` call. That will cause the `data` array to be redefined on every render, which will cause an infinite loop of re-renders. +The main thing to avoid is defining the `data` array inside the same scope as the `useTable` call. That will cause the `data` array to be redefined on every render, which will cause an infinite loop of re-renders. ```tsx export default function MyComponent() { @@ -227,8 +230,10 @@ export default function MyComponent() { // ... ]; - //❌ Columns and data are defined in the same scope as `useReactTable` without a stable reference, will cause infinite loop! - const table = useReactTable({ + //❌ Columns and data are defined in the same scope as `useTable` without a stable reference, will cause infinite loop! + const table = useTable({ + _features: tableFeatures({}), //❌ Also re-created on every render + _rowModels: {}, columns, data ?? [], //❌ Also bad because the fallback array is re-created on every render }); @@ -239,7 +244,7 @@ export default function MyComponent() { ### How TanStack Table Transforms Data -Later, in other parts of these docs, you will see how TanStack Table processes the `data` that you pass to the table and generates the row and cell objects that are used to create the table. The `data` that you pass to the table is never mutated by TanStack Table, but the actual values in the rows and cells may be transformed by the accessors in your column definitions, or by other features performed by [row models](../row-models) like grouping or aggregation. +Later, in other parts of these docs, you will see how TanStack Table processes the `data` that you pass to the table and generates the row and cell objects that are used to create the table. The `data` that you pass to the table is never mutated by TanStack Table, but the actual values in the rows and cells may be transformed by the accessors in your column definitions, or by other features performed by [row models](./row-models) like grouping or aggregation. ### How Much Data Can TanStack Table Handle? @@ -247,4 +252,4 @@ Believe it or not, TanStack Table was actually built to scale up to handle poten The default mindset of a developer building a data grid is to implement server-side pagination, sorting, and filtering for large datasets. This is still usually a good idea, but a lot of developers underestimate how much data can actually be handled in the client with modern browsers and the right optimizations. If your table will never have more than a few thousand rows, you can probably take advantage of the client-side features in TanStack Table instead of implementing them yourself on the server. Before committing to letting TanStack Table's client-side features handle your large dataset, you should test it with your actual data to see if it performs well enough for your needs, of course. -This is discussed in more detail in the [Pagination Guide](../pagination#should-you-use-client-side-pagination). +This is discussed in more detail in the [Pagination Guide](./pagination#should-you-use-client-side-pagination). diff --git a/docs/guide/expanding.md b/docs/guide/expanding.md index 8ca11daaf9..fe1fb0872e 100644 --- a/docs/guide/expanding.md +++ b/docs/guide/expanding.md @@ -6,13 +6,44 @@ title: Expanding Guide Want to skip to the implementation? Check out these examples: -- [expanding](../../framework/react/examples/expanding) -- [grouping](../../framework/react/examples/grouping) -- [sub-components](../../framework/react/examples/sub-components) + -## API +# React -[Expanding API](../../api/features/expanding) +- [Expanding](../framework/react/examples/expanding) +- [Sub Components](../framework/react/examples/sub-components) + +# Preact + +- [Expanding](../framework/preact/examples/expanding) +- [Sub Components](../framework/preact/examples/sub-components) + +# Solid + +- [Expanding](../framework/solid/examples/expanding) +- [Sub Components](../framework/solid/examples/sub-components) + +# Svelte + +- [Expanding](../framework/svelte/examples/expanding) +- [Sub Components](../framework/svelte/examples/sub-components) + +# Vue + +- [Expanding](../framework/vue/examples/expanding) +- [Sub Components](../framework/vue/examples/sub-components) + +# Angular + +- [Expanding](../framework/angular/examples/expanding) +- [Sub Components](../framework/angular/examples/sub-components) + +# Lit + +- [Expanding](../framework/lit/examples/expanding) +- [Sub Components](../framework/lit/examples/sub-components) + + ## Expanding Feature Guide @@ -27,12 +58,24 @@ There are multiple use cases for expanding features in TanStack Table that will ### Enable Client-Side Expanding -To use the client-side expanding features, you need to define the getExpandedRowModel function in your table options. This function is responsible for returning the expanded row model. +To use the client-side expanding features, add the `rowExpandingFeature` to your features and the `expandedRowModel` to your row models: ```ts -const table = useReactTable({ +import { + useTable, + tableFeatures, + rowExpandingFeature, + createExpandedRowModel, +} from '@tanstack/react-table' + +const _features = tableFeatures({ rowExpandingFeature }) + +const table = useTable({ + _features, + _rowModels: { + expandedRowModel: createExpandedRowModel(), + }, // other options... - getExpandedRowModel: getExpandedRowModel(), }) ``` @@ -74,11 +117,13 @@ const data: Person[] = [ Then you can use the getSubRows function to return the children array in each row as expanded rows. The table instance will now understand where to look for the sub rows on each row. ```ts -const table = useReactTable({ - // other options... +const table = useTable({ + _features, + _rowModels: { + expandedRowModel: createExpandedRowModel(), + }, getSubRows: (row) => row.children, // return the children array as sub-rows - getCoreRowModel: getCoreRowModel(), - getExpandedRowModel: getExpandedRowModel(), + // other options... }) ``` @@ -92,11 +137,13 @@ By default, the `row.getCanExpand()` row instance API will return false unless i ```ts //... -const table = useReactTable({ - // other options... +const table = useTable({ + _features, + _rowModels: { + expandedRowModel: createExpandedRowModel(), + }, getRowCanExpand: (row) => true, // Add your logic to determine if a row can be expanded. True means all rows include expanded data - getCoreRowModel: getCoreRowModel(), - getExpandedRowModel: getExpandedRowModel(), + // other options... }) //... @@ -106,10 +153,7 @@ const table = useReactTable({ {row.getVisibleCells().map((cell) => ( ))} @@ -134,12 +178,14 @@ If you need to control the expanded state of the rows in your table, you can do ```ts const [expanded, setExpanded] = useState({}) -const table = useReactTable({ +const table = useTable({ + _features, + _rowModels: { expandedRowModel: createExpandedRowModel() }, // other options... state: { - expanded: expanded, // must pass expanded state back to the table + expanded, }, - onExpandedChange: setExpanded + onExpandedChange: setExpanded, }) ``` @@ -187,14 +233,16 @@ By default, the filtering process starts from the parent rows and moves downward ```ts //... -const table = useReactTable({ - // other options... - getSubRows: row => row.subRows, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getExpandedRowModel: getExpandedRowModel(), +const table = useTable({ + _features: tableFeatures({ columnFilteringFeature, rowExpandingFeature }), + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + expandedRowModel: createExpandedRowModel(), + }, + getSubRows: (row) => row.subRows, filterFromLeafRows: true, // search through the expanded rows maxLeafRowFilterDepth: 1, // limit the depth of the expanded rows that are searched + // other options... }) ``` @@ -203,7 +251,9 @@ const table = useReactTable({ By default, expanded rows are paginated along with the rest of the table (which means expanded rows may span multiple pages). If you want to disable this behavior (which means expanded rows will always render on their parents page. This also means more rows will be rendered than the set page size) you can use the `paginateExpandedRows` option. ```ts -const table = useReactTable({ +const table = useTable({ + _features, + _rowModels: { expandedRowModel: createExpandedRowModel() }, // other options... paginateExpandedRows: false, }) @@ -222,7 +272,9 @@ By default, expanded rows are sorted along with the rest of the table. If you are doing server-side expansion, you can enable manual row expansion by setting the manualExpanding option to true. This means that the `getExpandedRowModel` will not be used to expand rows and you would be expected to perform the expansion in your own data model. ```ts -const table = useReactTable({ +const table = useTable({ + _features: tableFeatures({ rowExpandingFeature }), + _rowModels: {}, // no expandedRowModel needed for manual expanding // other options... manualExpanding: true, }) diff --git a/docs/guide/features.md b/docs/guide/features.md index 83d961c5b5..44fa796b27 100644 --- a/docs/guide/features.md +++ b/docs/guide/features.md @@ -2,20 +2,23 @@ title: Features Guide --- -TanStack Table comes with many features, each with their own associated options and API: +TanStack Table comes with many features, each with their own associated options and API. -- [Column Ordering](../column-ordering) -- [Column Pinning](../column-pinning) -- [Column Sizing](../column-sizing) -- [Column Visibility](../column-visibility) -- [Expanding](../expanding) -- [Column Faceting](../column-faceting) -- [Column Filtering](../column-filtering) -- [Global Faceting](../global-faceting) -- [Global Filtering](../global-filtering) -- [Grouping](../grouping) -- [Pagination](../pagination) -- [Row Pinning](../row-pinning) -- [Row Selection](../row-selection) -- [Sorting](../sorting) -- [Virtualization](../virtualization) \ No newline at end of file +> **v9 note:** In v9, features are opt-in. You declare which features your table uses via the `_features` option (using `tableFeatures()`). This enables tree-shaking—you only bundle the code for the features you need. See the [Table Instance Guide](./tables) and [Row Models Guide](./row-models) for setup. To include all features (v8-style), use `stockFeatures`. + +- [Column Ordering](./column-ordering) +- [Column Pinning](./column-pinning) +- [Column Sizing](./column-sizing) +- [Column Resizing](./column-resizing) +- [Column Visibility](./column-visibility) +- [Expanding](./expanding) +- [Column Faceting](./column-faceting) +- [Column Filtering](./column-filtering) +- [Global Faceting](./global-faceting) +- [Global Filtering](./global-filtering) +- [Grouping](./grouping) +- [Pagination](./pagination) +- [Row Pinning](./row-pinning) +- [Row Selection](./row-selection) +- [Sorting](./sorting) +- [Virtualization](./virtualization) diff --git a/docs/guide/filters.md b/docs/guide/filters.md index 1a5dfe5211..8d10bda84e 100644 --- a/docs/guide/filters.md +++ b/docs/guide/filters.md @@ -6,8 +6,8 @@ title: Filters Guide The filter guides are now split into multiple guides: -- [Column Filtering](../column-filtering) -- [Global Filtering](../global-filtering) -- [Fuzzy Filtering](../fuzzy-filtering) -- [Column Faceting](../column-faceting) -- [Global Faceting](../global-faceting) \ No newline at end of file +- [Column Filtering](./column-filtering) +- [Global Filtering](./global-filtering) +- [Fuzzy Filtering](./fuzzy-filtering) +- [Column Faceting](./column-faceting) +- [Global Faceting](./global-faceting) \ No newline at end of file diff --git a/docs/guide/fuzzy-filtering.md b/docs/guide/fuzzy-filtering.md index 6a42b8a839..8d7c09f68c 100644 --- a/docs/guide/fuzzy-filtering.md +++ b/docs/guide/fuzzy-filtering.md @@ -6,11 +6,33 @@ title: Fuzzy Filtering Guide Want to skip to the implementation? Check out these examples: -- [filters-fuzzy](../../framework/react/examples/filters-fuzzy) + -## API +# React -[Filters API](../../api/features/filters) +- [Fuzzy Search](../framework/react/examples/filters-fuzzy) + +# Preact + +- [Fuzzy Search](../framework/preact/examples/filters-fuzzy) + +# Solid + +- [Fuzzy Search](../framework/solid/examples/filters-fuzzy) + +# Svelte + +- [Fuzzy Search](../framework/svelte/examples/filters-fuzzy) + +# Vue + +- [Fuzzy Search](../framework/vue/examples/filters-fuzzy) + +# Lit + +- [Fuzzy Search](../framework/lit/examples/filters-fuzzy) + + ## Fuzzy Filtering Guide @@ -52,22 +74,37 @@ In this function, we're using the rankItem function from the @tanstack/match-sor To use fuzzy filtering with global filtering, you can specify the fuzzy filter function in the globalFilterFn option of the table instance: ```typescript -const table = useReactTable({ // or your framework's equivalent function - columns, - data, - filterFns: { - fuzzy: fuzzyFilter, //define as a filter function that can be used in column definitions - }, - globalFilterFn: 'fuzzy', //apply fuzzy filter to the global filter (most common use case for fuzzy filter) - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), //client side filtering - getSortedRowModel: getSortedRowModel(), //client side sorting needed if you want to use sorting too. +import { + useTable, + tableFeatures, + globalFilteringFeature, + rowSortingFeature, + createFilteredRowModel, + createSortedRowModel, + filterFns, + sortFns, +} from '@tanstack/react-table' + +const _features = tableFeatures({ globalFilteringFeature, rowSortingFeature }) + +const table = useTable({ + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel({ + ...filterFns, + fuzzy: fuzzyFilter, + }), + sortedRowModel: createSortedRowModel(sortFns), // needed if you want sorting with fuzzy rank + }, + columns, + data, + globalFilterFn: 'fuzzy', }) ``` ### Using Fuzzy Filtering with Column Filtering -To use fuzzy filtering with column filtering, you should first define the fuzzy filter function in the filterFns option of the table instance. You can then specify the fuzzy filter function in the filterFn option of the column definition: +To use fuzzy filtering with column filtering, pass your fuzzy filter function to `createFilteredRowModel` (merging it with the built-in `filterFns`). You can then specify the fuzzy filter by name in the `filterFn` option of the column definition: ```typescript const column = [ @@ -90,9 +127,9 @@ When using fuzzy filtering with column filtering, you might also want to sort th ```typescript import { compareItems } from '@tanstack/match-sorter-utils' -import { sortingFns } from '@tanstack/table' +import { sortFns } from '@tanstack/table' -const fuzzySort: SortingFn = (rowA, rowB, columnId) => { +const fuzzySort: SortFn = (rowA, rowB, columnId) => { let dir = 0 // Only sort by rank if the column has ranking information @@ -104,7 +141,7 @@ const fuzzySort: SortingFn = (rowA, rowB, columnId) => { } // Provide an alphanumeric fallback for when the item ranks are equal - return dir === 0 ? sortingFns.alphanumeric(rowA, rowB, columnId) : dir + return dir === 0 ? sortFns.alphanumeric(rowA, rowB, columnId) : dir } ``` diff --git a/docs/guide/global-faceting.md b/docs/guide/global-faceting.md index 708d2d53f4..2d67a10691 100644 --- a/docs/guide/global-faceting.md +++ b/docs/guide/global-faceting.md @@ -6,11 +6,33 @@ title: Global Faceting Guide Want to skip to the implementation? Check out these examples: -- [filters-faceted](../../framework/react/examples/filters) + -## API +# React -[Global Faceting API](../../api/features/global-faceting) +- [Faceted Filters](../framework/react/examples/filters-faceted) + +# Preact + +- [Faceted Filters](../framework/preact/examples/filters-faceted) + +# Solid + +- [Faceted Filters](../framework/solid/examples/filters-faceted) + +# Svelte + +- [Faceted Filters](../framework/svelte/examples/filters-faceted) + +# Vue + +- [Faceted Filters](../framework/vue/examples/filters-faceted) + +# Lit + +- [Faceted Filters](../framework/lit/examples/filters-faceted) + + ## Global Faceting Guide @@ -18,24 +40,27 @@ Global Faceting allows you to generate lists of values for all columns from the ### Global Faceting Row Models -In order to use any of the global faceting features, you must include the appropriate row models in your table options. +In order to use any of the global faceting features, add the appropriate faceted row models to your `_rowModels`: ```ts -//only import the row models you need import { - getCoreRowModel, - getFacetedRowModel, - getFacetedMinMaxValues, //depends on getFacetedRowModel - getFacetedUniqueValues, //depends on getFacetedRowModel + useTable, + tableFeatures, + createFacetedRowModel, + createFacetedMinMaxValues, + createFacetedUniqueValues, } from '@tanstack/react-table' -//... -const table = useReactTable({ + +const _features = tableFeatures({}) // add globalFilteringFeature if using global filtering + +const table = useTable({ + _features, + _rowModels: { + facetedRowModel: createFacetedRowModel(), // required (other faceting methods depend on this) + facetedMinMaxValues: createFacetedMinMaxValues(), // if you need min/max values + facetedUniqueValues: createFacetedUniqueValues(), // if you need a list of unique values + }, // other options... - getCoreRowModel: getCoreRowModel(), - getFacetedRowModel: getFacetedRowModel(), //Faceting model for client-side faceting (other faceting methods depend on this model) - getFacetedMinMaxValues: getFacetedMinMaxValues(), //if you need min/max values - getFacetedUniqueValues: getFacetedUniqueValues(), //if you need a list of unique values - //... }) ``` diff --git a/docs/guide/global-filtering.md b/docs/guide/global-filtering.md index 8006eb0abc..26c1708031 100644 --- a/docs/guide/global-filtering.md +++ b/docs/guide/global-filtering.md @@ -6,11 +6,49 @@ title: Global Filtering Guide Want to skip to the implementation? Check out these examples: -- [Global Filters](../../framework/react/examples/filters-global) + -## API +# React -[Global Filtering API](../../api/features/global-filtering) +- [Column Filters](../framework/react/examples/filters) +- [Faceted Filters](../framework/react/examples/filters-faceted) +- [Fuzzy Search](../framework/react/examples/filters-fuzzy) + +# Preact + +- [Column Filters](../framework/preact/examples/filters) +- [Faceted Filters](../framework/preact/examples/filters-faceted) +- [Fuzzy Search](../framework/preact/examples/filters-fuzzy) + +# Solid + +- [Column Filters](../framework/solid/examples/filters) +- [Faceted Filters](../framework/solid/examples/filters-faceted) +- [Fuzzy Search](../framework/solid/examples/filters-fuzzy) + +# Svelte + +- [Column Filters](../framework/svelte/examples/filtering) +- [Faceted Filters](../framework/svelte/examples/filters-faceted) +- [Fuzzy Search](../framework/svelte/examples/filters-fuzzy) + +# Vue + +- [Column Filters](../framework/vue/examples/filters) +- [Faceted Filters](../framework/vue/examples/filters-faceted) +- [Fuzzy Search](../framework/vue/examples/filters-fuzzy) + +# Angular + +- [Column Filters](../framework/angular/examples/filters) + +# Lit + +- [Column Filters](../framework/lit/examples/filters) +- [Faceted Filters](../framework/lit/examples/filters-faceted) +- [Fuzzy Search](../framework/lit/examples/filters-fuzzy) + + ## Global Filtering Guide @@ -22,7 +60,7 @@ This guide will focus on global filtering, which is a filter that is applied acr If you have a large dataset, you may not want to load all of that data into the client's browser in order to filter it. In this case, you will most likely want to implement server-side filtering, sorting, pagination, etc. -However, as also discussed in the [Pagination Guide](../pagination#should-you-use-client-side-pagination), a lot of developers underestimate how many rows can be loaded client-side without a performance hit. The TanStack table examples are often tested to handle up to 100,000 rows or more with decent performance for client-side filtering, sorting, pagination, and grouping. This doesn't necessarily mean that your app will be able to handle that many rows, but if your table is only going to have a few thousand rows at most, you might be able to take advantage of the client-side filtering, sorting, pagination, and grouping that TanStack table provides. +However, as also discussed in the [Pagination Guide](./pagination#should-you-use-client-side-pagination), a lot of developers underestimate how many rows can be loaded client-side without a performance hit. The TanStack table examples are often tested to handle up to 100,000 rows or more with decent performance for client-side filtering, sorting, pagination, and grouping. This doesn't necessarily mean that your app will be able to handle that many rows, but if your table is only going to have a few thousand rows at most, you might be able to take advantage of the client-side filtering, sorting, pagination, and grouping that TanStack table provides. > TanStack Table can handle thousands of client-side rows with good performance. Don't rule out client-side filtering, pagination, sorting, etc. without some thought first. @@ -38,13 +76,22 @@ If you're not sure, you can always start with client-side filtering and paginati If you have decided that you need to implement server-side global filtering instead of using the built-in client-side global filtering, here's how you do that. -No `getFilteredRowModel` table option is needed for manual server-side global filtering. Instead, the `data` that you pass to the table should already be filtered. However, if you have passed a `getFilteredRowModel` table option, you can tell the table to skip it by setting the `manualFiltering` option to `true`. +No `filteredRowModel` is needed for manual server-side global filtering. Instead, the `data` that you pass to the table should already be filtered. However, if you have added a `filteredRowModel` to `_rowModels`, you can tell the table to skip it by setting the `manualFiltering` option to `true`. ```jsx -const table = useReactTable({ +import { + useTable, + tableFeatures, + globalFilteringFeature, +} from '@tanstack/react-table' + +const _features = tableFeatures({ globalFilteringFeature }) + +const table = useTable({ + _features, + _rowModels: {}, // no filteredRowModel needed for manual server-side global filtering data, columns, - // getFilteredRowModel: getFilteredRowModel(), // not needed for manual server-side global filtering manualFiltering: true, }) ``` @@ -53,15 +100,25 @@ Note: When using manual global filtering, many of the options that are discussed ### Client-Side Global Filtering -If you are using the built-in client-side global filtering, first you need to pass in a getFilteredRowModel function to the table options. +If you are using the built-in client-side global filtering, add the `globalFilteringFeature` to your features and the `filteredRowModel` to your row models: ```jsx -import { useReactTable, getFilteredRowModel } from '@tanstack/react-table' -//... -const table = useReactTable({ +import { + useTable, + tableFeatures, + globalFilteringFeature, + createFilteredRowModel, + filterFns, +} from '@tanstack/react-table' + +const _features = tableFeatures({ globalFilteringFeature }) + +const table = useTable({ + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + }, // other options... - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), // needed for client-side global filtering }) ``` @@ -70,12 +127,14 @@ const table = useReactTable({ The globalFilterFn option allows you to specify the filter function that will be used for global filtering. The filter function can be a string that references a built-in filter function, a string that references a custom filter function provided via the tableOptions.filterFns option, or a custom filter function. ```jsx -const table = useReactTable({ +const table = useTable({ + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + }, data, columns, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - globalFilterFn: 'text' // built-in filter function + globalFilterFn: 'text', // built-in filter function }) ``` @@ -96,17 +155,19 @@ You can also define your own custom filter functions either as the globalFilterF ### Global Filter State -The global filter state is stored in the table's internal state and can be accessed via the table.getState().globalFilter property. If you want to persist the global filter state outside of the table, you can use the onGlobalFilterChange option to provide a callback function that will be called whenever the global filter state changes. +The global filter state is stored in the table's state atoms and can be read with `table.atoms.globalFilter.get()` or from the current `table.store.state.globalFilter` snapshot. If you want to persist the global filter state outside of the table, use the `state.globalFilter` and `onGlobalFilterChange` options together. ```jsx const [globalFilter, setGlobalFilter] = useState([]) -const table = useReactTable({ +const table = useTable({ + _features, + _rowModels: { filteredRowModel: createFilteredRowModel(filterFns) }, // other options... state: { globalFilter, }, - onGlobalFilterChange: setGlobalFilter + onGlobalFilterChange: setGlobalFilter, }) ``` @@ -145,9 +206,11 @@ const customFilterFn = (rows, columnId, filterValue) => { // custom filter logic } -const table = useReactTable({ +const table = useTable({ + _features, + _rowModels: { filteredRowModel: createFilteredRowModel(filterFns) }, // other options... - globalFilterFn: customFilterFn + globalFilterFn: customFilterFn, }) ``` @@ -160,14 +223,16 @@ However, you can also just specify the initial global filter state in the state. ```jsx const [globalFilter, setGlobalFilter] = useState("search term") //recommended to initialize globalFilter state here -const table = useReactTable({ +const table = useTable({ + _features, + _rowModels: { filteredRowModel: createFilteredRowModel(filterFns) }, // other options... initialState: { globalFilter: 'search term', // if not managing globalFilter state, set initial state here - } + }, state: { globalFilter, // pass our managed globalFilter state to the table - } + }, }) ``` @@ -189,7 +254,9 @@ const columns = [ //... ] //... -const table = useReactTable({ +const table = useTable({ + _features, + _rowModels: { filteredRowModel: createFilteredRowModel(filterFns) }, // other options... columns, enableGlobalFilter: false, // disable global filtering for all columns diff --git a/docs/guide/grouping.md b/docs/guide/grouping.md index 0bae282711..e476a5b82a 100644 --- a/docs/guide/grouping.md +++ b/docs/guide/grouping.md @@ -6,30 +6,67 @@ title: Grouping Guide Want to skip to the implementation? Check out these examples: -- [grouping](../../framework/react/examples/grouping) + -## API +# React -[Grouping API](../../api/features/grouping) +- [Grouping](../framework/react/examples/grouping) + +# Preact + +- [Grouping](../framework/preact/examples/grouping) + +# Solid + +- [Grouping](../framework/solid/examples/grouping) + +# Svelte + +- [Grouping](../framework/svelte/examples/grouping) + +# Vue + +- [Grouping](../framework/vue/examples/grouping) + +# Angular + +- [Grouping](../framework/angular/examples/grouping) + +# Lit + +- [Grouping](../framework/lit/examples/grouping) + + ## Grouping Guide There are 3 table features that can reorder columns, which happen in the following order: -1. [Column Pinning](../column-pinning) - If pinning, columns are split into left, center (unpinned), and right pinned columns. -2. Manual [Column Ordering](../column-ordering) - A manually specified column order is applied. +1. [Column Pinning](./column-pinning) - If pinning, columns are split into left, center (unpinned), and right pinned columns. +2. Manual [Column Ordering](./column-ordering) - A manually specified column order is applied. 3. **Grouping** - If grouping is enabled, a grouping state is active, and `tableOptions.groupedColumnMode` is set to `'reorder' | 'remove'`, then the grouped columns are reordered to the start of the column flow. Grouping in TanStack table is a feature that applies to columns and allows you to categorize and organize the table rows based on specific columns. This can be useful in cases where you have a large amount of data and you want to group them together based on certain criteria. -To use the grouping feature, you will need to use the grouped row model. This model is responsible for grouping the rows based on the grouping state. +To use the grouping feature, add the `columnGroupingFeature` to your features and the `groupedRowModel` to your row models. The grouped row model is responsible for grouping the rows based on the grouping state. ```tsx -import { getGroupedRowModel } from '@tanstack/react-table' - -const table = useReactTable({ +import { + useTable, + tableFeatures, + columnGroupingFeature, + createGroupedRowModel, + aggregationFns, +} from '@tanstack/react-table' + +const _features = tableFeatures({ columnGroupingFeature }) + +const table = useTable({ + _features, + _rowModels: { + groupedRowModel: createGroupedRowModel(aggregationFns), + }, // other options... - getGroupedRowModel: getGroupedRowModel(), }) ``` @@ -37,12 +74,15 @@ When grouping state is active, the table will add matching rows as subRows to th To allow the user to expand and collapse the grouped rows, you can use the expanding feature. ```tsx -import { getGroupedRowModel, getExpandedRowModel} from '@tanstack/react-table' +const _features = tableFeatures({ columnGroupingFeature, rowExpandingFeature }) -const table = useReactTable({ +const table = useTable({ + _features, + _rowModels: { + groupedRowModel: createGroupedRowModel(aggregationFns), + expandedRowModel: createExpandedRowModel(), + }, // other options... - getGroupedRowModel: getGroupedRowModel(), - getExpandedRowModel: getExpandedRowModel(), }) ``` @@ -63,7 +103,9 @@ table.resetGrouping(); By default, when a column is grouped, it is moved to the start of the table. You can control this behavior using the groupedColumnMode option. If you set it to 'reorder', then the grouped columns will be moved to the start of the table. If you set it to 'remove', then the grouped columns will be removed from the table. If you set it to false, then the grouped columns will not be moved or removed. ```tsx -const table = useReactTable({ +const table = useTable({ + _features, + _rowModels: { groupedRowModel: createGroupedRowModel(aggregationFns) }, // other options... groupedColumnMode: 'reorder', }) @@ -99,13 +141,17 @@ There are several built-in aggregation functions that you can use: When rows are grouped, you can aggregate the data in the grouped rows using the aggregationFns option. This is a record where the keys are the IDs of the aggregation functions, and the values are the aggregation functions themselves. You can then reference these aggregation functions in a column's aggregationFn option. ```tsx -const table = useReactTable({ - // other options... - aggregationFns: { - myCustomAggregation: (columnId, leafRows, childRows) => { - // return the aggregated value - }, +const table = useTable({ + _features, + _rowModels: { + groupedRowModel: createGroupedRowModel({ + ...aggregationFns, + myCustomAggregation: (columnId, leafRows, childRows) => { + // return the aggregated value + }, + }), }, + // other options... }) ``` @@ -122,7 +168,9 @@ const column = columnHelper.accessor('key', { If you are doing server-side grouping and aggregation, you can enable manual grouping using the manualGrouping option. When this option is set to true, the table will not automatically group rows using getGroupedRowModel() and instead will expect you to manually group the rows before passing them to the table. ```tsx -const table = useReactTable({ +const table = useTable({ + _features: tableFeatures({ columnGroupingFeature }), + _rowModels: {}, // no groupedRowModel needed for manual grouping // other options... manualGrouping: true, }) @@ -137,11 +185,13 @@ If you want to manage the grouping state yourself, you can use the onGroupingCha ```tsx const [grouping, setGrouping] = useState([]) -const table = useReactTable({ +const table = useTable({ + _features, + _rowModels: { groupedRowModel: createGroupedRowModel(aggregationFns) }, // other options... state: { - grouping: grouping, + grouping, }, - onGroupingChange: setGrouping + onGroupingChange: setGrouping, }) ``` diff --git a/docs/guide/header-groups.md b/docs/guide/header-groups.md index 0ea1754cf2..c3d9ab6b28 100644 --- a/docs/guide/header-groups.md +++ b/docs/guide/header-groups.md @@ -2,17 +2,13 @@ title: Header Groups Guide --- -## API - -[Header Group API](../../api/core/header-group) - ## Header Groups Guide This quick guide will discuss the different ways you can retrieve and interact with header group objects in TanStack Table. ### What are Header Groups? -Header Groups are simply "rows" of headers. Don't let the name confuse you, it's just that simple. The large majority of tables will only have one row of headers (a single header group), but if you define your column structure with nested columns as with the [Column Groups example](../../framework/react/examples/column-groups), you can have multiple rows of headers (multiple header groups). +Header Groups are simply "rows" of headers. Don't let the name confuse you, it's just that simple. The large majority of tables will only have one row of headers (a single header group), but if you define your column structure with nested columns as with the [Column Groups example](../framework/react/examples/column-groups), you can have multiple rows of headers (multiple header groups). ### Where to Get Header Groups From @@ -20,13 +16,13 @@ There are multiple `table` instance APIs you can use to retrieve header groups f ### Header Group Objects -Header Group objects are similar to [Row](../rows) objects, though simpler since there is not as much going on in header rows as there are in the body rows. +Header Group objects are similar to [Row](./rows) objects, though simpler since there is not as much going on in header rows as there are in the body rows. By default, header groups only have three properties: - `id`: The unique identifier for the header group that is generated from its depth (index). This is useful as a key for React components. - `depth`: The depth of the header group, zero-indexed based. Think of this as the row index amongst all header rows. -- `headers`: An array of [Header](../headers) cell objects that belong to this header group (row). +- `headers`: An array of [Header](./headers) cell objects that belong to this header group (row). ### Access Header Cells @@ -46,4 +42,4 @@ To render the header cells in a header group, you just map over the `headers` ar ) })} -``` \ No newline at end of file +``` diff --git a/docs/guide/headers.md b/docs/guide/headers.md index 97623c908c..f8f5145823 100644 --- a/docs/guide/headers.md +++ b/docs/guide/headers.md @@ -2,10 +2,6 @@ title: Headers Guide --- -## API - -[Header API](../../api/core/header) - ## Headers Guide This quick guide will discuss the different ways you can retrieve and interact with `header` objects in TanStack Table. @@ -14,7 +10,7 @@ Headers are the equivalent of cells, but meant for the `` section of the ### Where to Get Headers From -Headers come from [Header Groups](../header-groups), which are the equivalent of rows, but meant for the `` section of the table instead of the `` section. +Headers come from [Header Groups](./header-groups), which are the equivalent of rows, but meant for the `` section of the table instead of the `` section. #### HeaderGroup Headers @@ -42,11 +38,11 @@ There are multiple `table` instance APIs that you can use to retrieve a list of ### Header Objects -Header objects are similar to [Cell](../cells) objects, but meant for the `` section of the table instead of the `` section. Every header object can be associated with a `` section of the table instead of the `` section. Every header object can be associated with a ` -))} \ No newline at end of file +))} diff --git a/docs/guide/migrating.md b/docs/guide/migrating.md deleted file mode 100644 index 1aae1c911c..0000000000 --- a/docs/guide/migrating.md +++ /dev/null @@ -1,195 +0,0 @@ ---- -title: Migrating to V8 Guide ---- - -## Migrating to V8 - -TanStack Table V8 was a major rewrite of React Table v7 from the ground up in TypeScript. The overall structure/organization of your markup and CSS will largely remain the same, but many of the APIs have been renamed or replaced. - -### Notable Changes - -- Full rewrite to TypeScript with types included in the base package -- Removal of plugin system to favor more inversion of control -- Vastly larger and improved API (and new features like pinning) -- Better controlled state management -- Better support for server-side operations -- Complete (but optional) data pipeline control -- Agnostic core with framework adapters for React, Solid, Svelte, Vue, and potentially more in the future -- New Dev Tools - -### Install the new Version - -The new version of TanStack Table is published under the `@tanstack` scope. Install the new package using your favorite package manager: - -```bash -npm uninstall react-table @types/react-table -npm install @tanstack/react-table -``` - -```tsx -- import { useTable } from 'react-table' // [!code --] -+ import { useReactTable } from '@tanstack/react-table' // [!code ++] -``` - -Types are now included in the base package, so you can remove the `@types/react-table` package. - -> If you want, you can keep the old `react-table` packages installed so that you can gradually migrate your code. You should be able to use both packages side-by-side for separate tables without any issues. - -### Update Table Options - -- Rename `useTable` to `useReactTable` -- The old hook and plugin systems have been removed, but they have replaced with tree-shakable row model imports for each feature. - -```tsx -- import { useTable, usePagination, useSortBy } from 'react-table'; // [!code --] -+ import { // [!code ++] -+ useReactTable, // [!code ++] -+ getCoreRowModel, // [!code ++] -+ getPaginationRowModel, // [!code ++] -+ getSortedRowModel // [!code ++] -+ } from '@tanstack/react-table'; // [!code ++] - -// ... - -- const tableInstance = useTable( // [!code --] -- { columns, data }, // [!code --] -- useSortBy, // [!code --] -- usePagination, //order of hooks used to matter // [!code --] -- // etc. // [!code --] -- ); // [!code --] -+ const tableInstance = useReactTable({ // [!code ++] -+ columns, // [!code ++] -+ data, // [!code ++] -+ getCoreRowModel: getCoreRowModel(), // [!code ++] -+ getPaginationRowModel: getPaginationRowModel(), // [!code ++] -+ getSortedRowModel: getSortedRowModel(), //order doesn't matter anymore! // [!code ++] -+ // etc. // [!code ++] -+ }); // [!code ++] -``` - -- All `disable*` table options were renamed to `enable*` table options. (e.g. `disableSortBy` is now `enableSorting`, `disableGroupBy` is now `enableGrouping`, etc.) -- ... - -### Update column definitions - -- accessor was renamed to either `accessorKey` or `accessorFn` (depending on whether you are using a string or function) -- width, minWidth, maxWidth were renamed to size, minSize, maxSize -- Optionally, you can use the new `createColumnHelper` function around each column definition for better TypeScript hints. (You can still just use an array of column definitions if you prefer.) - - The first parameter is the accessor function or accessor string. - - The second parameter is an object of column options. - -```tsx -const columns = [ -- { // [!code --] -- accessor: 'firstName', // [!code --] -- Header: 'First Name', // [!code --] -- }, // [!code --] -- { // [!code --] -- accessor: row => row.lastName, // [!code --] -- Header: () => Last Name, // [!code --] -- }, // [!code --] - -// Best TypeScript experience, especially when using `cell.getValue()` later on -+ columnHelper.accessor('firstName', { //accessorKey // [!code ++] -+ header: 'First Name', // [!code ++] -+ }), // [!code ++] -+ columnHelper.accessor(row => row.lastName, { //accessorFn // [!code ++] -+ header: () => Last Name, // [!code ++] -+ }), // [!code ++] - -// OR (if you prefer) -+ { // [!code ++] -+ accessorKey: 'firstName', // [!code ++] -+ header: 'First Name', // [!code ++] -+ }, // [!code ++] -+ { // [!code ++] -+ accessorFn: row => row.lastName, // [!code ++] -+ header: () => Last Name, // [!code ++] -+ }, // [!code ++] -] -``` - -> Note: If defining columns inside a component, you should still try to give the column definitions a stable identity. This will help with performance and prevent unnecessary re-renders. Store the column definitions in either a `useMemo` or `useState` hook. - -- Column Option Name Changes - - - `Header` was renamed to `header` - - `Cell` was renamed to `cell` (The cell render function has also changed. See below) - - `Footer` was renamed to `footer` - - All `disable*` column options were renamed to `enable*` column options. (e.g. `disableSortBy` is now `enableSorting`, `disableGroupBy` is now `enableGrouping`, etc.) - - `sortType` `sortingFn` - - ... - -- Changes to custom cell renderers - - - `value` was renamed `getValue` (Throughout the upgrade, instead of providing the value directly, a function `getValue` is exposed for evaluating the value. This change aims to improve performance by evaluating the value only when `getValue()` is called and then caching it.) - - `cell: { isGrouped, isPlaceholder, isAggregated }` is now `cell: { getIsGrouped, getIsPlaceholder, getIsAggregated }` - - `column`: The base level props are now RT-specific. Values that you added to the object when defining it are now one level deeper in `columnDef`. - - `table`: Props passed into the `useTable` hook now appear under `options`. - -### Migrate Table Markup - -- Use `flexRender()` instead of `cell.render('Cell')` or `column.render('Header')`, etc. -- `getHeaderProps`, `getFooterProps`, `getCellProps`, `getRowProps`, etc. have all been _deprecated_. - - TanStack Table does not provide any default `style` or accessibility attributes like `role` anymore. These are still important for you to get right, but it had to be removed in order to support being framework-agnostic. - - You will need to define `onClick` handlers manually, but there are new `get*Handler` helpers to keep this simple. - - You will need to define the `key` props manually - - You will need to define the `colSpan` prop manually if using features that require it (grouped headers, aggregation, etc.) - -```tsx -- // [!code --] -+ // [!code ++] -``` - -```tsx -- // [!code --] -+ // [!code ++] -``` - -```tsx -// in column definitions in this case -- Header: ({ getToggleAllRowsSelectedProps }) => ( // [!code --] -- // [!code --] -- ), // [!code --] -- Cell: ({ row }) => ( // [!code --] -- // [!code --] -- ), // [!code --] -+ header: ({ table }) => ( // [!code ++] -+ // [!code ++] -+ ), // [!code ++] -+ cell: ({ row }) => ( // [!code ++] -+ // [!code ++] -+ ), // [!code ++] -``` - -### Other Changes - -- custom `filterTypes` (now called `filterFns`) have a new function signature as it only returns a boolean for whether the row should be included or not. - -```tsx -- (rows: Row[], id: string, filterValue: any) => Row[] // [!code --] -+ (row: Row, id: string, filterValue: any) => boolean // [!code ++] -``` - -- ... - -> This guide is a work in progress. Please consider contributing to it if you have time! diff --git a/docs/guide/pagination.md b/docs/guide/pagination.md index 8ff1aec367..2edba0ab4b 100644 --- a/docs/guide/pagination.md +++ b/docs/guide/pagination.md @@ -6,17 +6,46 @@ title: Pagination Guide Want to skip to the implementation? Check out these examples: -- [pagination](../../framework/react/examples/pagination) -- [pagination-controlled (React Query)](../../framework/react/examples/pagination-controlled) -- [editable-data](../../framework/react/examples/editable-data) -- [expanding](../../framework/react/examples/expanding) -- [filters](../../framework/react/examples/filters) -- [fully-controlled](../../framework/react/examples/fully-controlled) -- [row-selection](../../framework/react/examples/row-selection) + -## API +# React -[Pagination API](../../api/features/pagination) +- [Pagination](../framework/react/examples/pagination) +- [With TanStack Query](../framework/react/examples/with-tanstack-query) + +# Preact + +- [Pagination](../framework/preact/examples/pagination) +- [With TanStack Query](../framework/preact/examples/with-tanstack-query) + +# Solid + +- [Pagination](../framework/solid/examples/pagination) +- [With TanStack Query](../framework/solid/examples/with-tanstack-query) + +# Svelte + +- [Pagination](../framework/svelte/examples/pagination) +- [With TanStack Query](../framework/svelte/examples/with-tanstack-query) + +# Vue + +- [Pagination](../framework/vue/examples/pagination) +- [With TanStack Query](../framework/vue/examples/with-tanstack-query) + +# Angular + +- [Remote Data](../framework/angular/examples/remote-data) + +# Lit + +- [Pagination](../framework/lit/examples/pagination) + +# Vanilla + +- [Pagination](../framework/vanilla/examples/pagination) + + ## Pagination Guide @@ -30,7 +59,7 @@ Using client-side pagination means that the `data` that you fetch will contain * Client-side pagination is usually the simplest way to implement pagination when using TanStack Table, but it might not be practical for very large datasets. -However, a lot of people underestimate just how much data can be handled client-side. If your table will only ever have a few thousand rows or less, client-side pagination can still be a viable option. TanStack Table is designed to scale up to 10s of thousands of rows with decent performance for pagination, filtering, sorting, and grouping. The [official pagination example](../../framework/react/examples/pagination) loads 100,000 rows and still performs well, albeit with only handful of columns. +However, a lot of people underestimate just how much data can be handled client-side. If your table will only ever have a few thousand rows or less, client-side pagination can still be a viable option. TanStack Table is designed to scale up to 10s of thousands of rows with decent performance for pagination, filtering, sorting, and grouping. The [official pagination example](../framework/react/examples/pagination) loads 100,000 rows and still performs well, albeit with only handful of columns. Every use-case is different and will depend on the complexity of the table, how many columns you have, how large every piece of data is, etc. The main bottlenecks to pay attention to are: @@ -46,41 +75,58 @@ Alternatively, instead of paginating the data, you can render all rows of a larg #### Pagination Row Model -If you want to take advantage of the built-in client-side pagination in TanStack Table, you first need to pass in the pagination row model. +If you want to take advantage of the built-in client-side pagination in TanStack Table, add the `rowPaginationFeature` to your features and the `paginatedRowModel` to your row models: ```jsx -import { useReactTable, getCoreRowModel, getPaginationRowModel } from '@tanstack/react-table'; -//... -const table = useReactTable({ +import { + useTable, + tableFeatures, + rowPaginationFeature, + createPaginatedRowModel, +} from '@tanstack/react-table' + +const _features = tableFeatures({ rowPaginationFeature }) + +const table = useTable({ + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + }, columns, data, - getCoreRowModel: getCoreRowModel(), - getPaginationRowModel: getPaginationRowModel(), //load client-side pagination code -}); +}) ``` +> **Migrating from v8?** See [useLegacyTable](../framework/react/guide/use-legacy-table) for incremental migration. + ### Manual Server-Side Pagination If you decide that you need to use server-side pagination, here is how you can implement it. -No pagination row model is needed for server-side pagination, but if you have provided it for other tables that do need it in a shared component, you can still turn off the client-side pagination by setting the `manualPagination` option to `true`. Setting the `manualPagination` option to `true` will tell the table instance to use the `table.getPrePaginationRowModel` row model under the hood, and it will make the table instance assume that the `data` that you pass in is already paginated. +No pagination row model is needed for server-side pagination, but if you have provided it for other tables that do need it in a shared component, you can still turn off the client-side pagination by setting the `manualPagination` option to `true`. Setting the `manualPagination` option to `true` will tell the table instance to use the `table.getPrePaginatedRowModel` row model under the hood, and it will make the table instance assume that the `data` that you pass in is already paginated. #### Page Count and Row Count The table instance will have no way of knowing how many rows/pages there are in total in your back-end unless you tell it. Provide either the `rowCount` or `pageCount` table option to let the table instance know how many pages there are in total. If you provide a `rowCount`, the table instance will calculate the `pageCount` internally from `rowCount` and `pageSize`. Otherwise, you can directly provide the `pageCount` if you already have it. If you don't know the page count, you can just pass in `-1` for the `pageCount`, but the `getCanNextPage` and `getCanPreviousPage` row model functions will always return `true` in this case. ```jsx -import { useReactTable, getCoreRowModel, getPaginationRowModel } from '@tanstack/react-table'; -//... -const table = useReactTable({ +import { + useTable, + tableFeatures, + rowPaginationFeature, +} from '@tanstack/react-table' + +const _features = tableFeatures({ rowPaginationFeature }) + +const table = useTable({ + _features, + _rowModels: {}, // no paginatedRowModel needed for server-side pagination columns, data, - getCoreRowModel: getCoreRowModel(), - // getPaginationRowModel: getPaginationRowModel(), //not needed for server-side pagination - manualPagination: true, //turn off client-side pagination - rowCount: dataQuery.data?.rowCount, //pass in the total row count so the table knows how many pages there are (pageCount calculated internally if not provided) - // pageCount: dataQuery.data?.pageCount, //alternatively directly pass in pageCount instead of rowCount -}); + manualPagination: true, // turn off client-side pagination + rowCount: dataQuery.data?.rowCount, // pass in the total row count so the table knows how many pages there are (pageCount calculated internally if not provided) + // pageCount: dataQuery.data?.pageCount, // alternatively directly pass in pageCount instead of rowCount +}) ``` > **Note**: Setting the `manualPagination` option to `true` will make the table instance assume that the `data` that you pass in is already paginated. @@ -97,41 +143,51 @@ The `pagination` state is an object that contains the following properties: You can manage the `pagination` state just like any other state in the table instance. ```jsx -import { useReactTable, getCoreRowModel, getPaginationRowModel } from '@tanstack/react-table'; -//... -const [pagination, setPagination] = useState({ - pageIndex: 0, //initial page index - pageSize: 10, //default page size -}); +import { + useTable, + tableFeatures, + rowPaginationFeature, + createPaginatedRowModel, +} from '@tanstack/react-table' + +const _features = tableFeatures({ rowPaginationFeature }) -const table = useReactTable({ +const [pagination, setPagination] = useState({ + pageIndex: 0, // initial page index + pageSize: 10, // default page size +}) + +const table = useTable({ + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + }, columns, data, - getCoreRowModel: getCoreRowModel(), - getPaginationRowModel: getPaginationRowModel(), - onPaginationChange: setPagination, //update the pagination state when internal APIs mutate the pagination state + onPaginationChange: setPagination, state: { - //... pagination, }, -}); +}) ``` Alternatively, if you have no need for managing the `pagination` state in your own scope, but you need to set different initial values for the `pageIndex` and `pageSize`, you can use the `initialState` option. ```jsx -const table = useReactTable({ +const table = useTable({ + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + }, columns, data, - getCoreRowModel: getCoreRowModel(), - getPaginationRowModel: getPaginationRowModel(), initialState: { pagination: { - pageIndex: 2, //custom initial page index - pageSize: 25, //custom default page size + pageIndex: 2, // custom initial page index + pageSize: 25, // custom default page size }, }, -}); +}) ``` > **Note**: Do NOT pass the `pagination` state to both the `state` and `initialState` options. `state` will overwrite `initialState`. Only use one or the other. @@ -145,13 +201,15 @@ Besides the `manualPagination`, `pageCount`, and `rowCount` options which are us By default, `pageIndex` is reset to `0` when page-altering state changes occur, such as when the `data` is updated, filters change, grouping changes, etc. This behavior is automatically disabled when `manualPagination` is true but it can be overridden by explicitly assigning a boolean value to the `autoResetPageIndex` table option. ```jsx -const table = useReactTable({ +const table = useTable({ + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + }, columns, data, - getCoreRowModel: getCoreRowModel(), - getPaginationRowModel: getPaginationRowModel(), - autoResetPageIndex: false, //turn off auto reset of pageIndex -}); + autoResetPageIndex: false, // turn off auto reset of pageIndex +}) ``` Be aware, however, that if you turn off `autoResetPageIndex`, you may need to add some logic to handle resetting the `pageIndex` yourself to avoid showing empty pages. @@ -175,7 +233,7 @@ There are several pagination table instance APIs that are useful for hooking up - `setPagination`: Useful for setting all of the pagination state at once. - `resetPagination`: Useful for resetting the table state to the original pagination state. -> **Note**: Some of these APIs are new in `v8.13.0`. +> **Note**: These pagination APIs are available when using `rowPaginationFeature`. ```jsx
+``` + +Though, as discussed in the [advanced column resizing performance section](#advanced-column-resizing-performance), you may want to consider using CSS variables to apply column sizes to your markup. + +#### Column Resize APIs + +TanStack Table provides a pre-built event handler to make your drag interactions easy to implement. These event handlers are just convenience functions that call other internal APIs to update the column sizing state and re-render the table. Use `header.getResizeHandler()` to connect to your column resize drag interactions, for both mouse and touch events. + +```tsx + +``` + +#### Column Resize Indicator with ColumnSizingInfoState + +TanStack Table keeps track of a state object called `columnSizingInfo` that you can use to render a column resize indicator UI. + +```jsx + +``` + +### Advanced Column Resizing Performance + +If you are creating large or complex tables (and using React 😉), you may find that if you do not add proper memoization to your render logic, your users may experience degraded performance while resizing columns. + +We have created a [performant column resizing example](../framework/react/examples/column-resizing-performant) that demonstrates how to achieve 60 fps column resizing renders with a complex table that may otherwise have slow renders. It is recommended that you just look at that example to see how it is done, but these are the basic things to keep in mind: + +1. Don't use `column.getSize()` on every header and every data cell. Instead, calculate all column widths once upfront, **memoized**! +2. Memoize your Table Body while resizing is in progress. +3. Use CSS variables to communicate column widths to your table cells. + +If you follow these steps, you should see significant performance improvements while resizing columns. + +If you are not using React, and are using the Svelte, Vue, or Solid adapters instead, you may not need to worry about this as much, but similar principles apply. diff --git a/docs/guide/column-sizing.md b/docs/guide/column-sizing.md index a4212559da..a5b0441787 100644 --- a/docs/guide/column-sizing.md +++ b/docs/guide/column-sizing.md @@ -6,16 +6,39 @@ title: Column Sizing Guide Want to skip to the implementation? Check out these examples: -- [column-sizing](../../framework/react/examples/column-sizing) -- [column-resizing-performant](../../framework/react/examples/column-resizing-performant) + -## API +# React -[Column Sizing API](../../api/features/column-sizing) +- [Column Sizing](../framework/react/examples/column-sizing) + +# Preact + +- [Column Sizing](../framework/preact/examples/column-sizing) + +# Solid + +- [Column Sizing](../framework/solid/examples/column-sizing) + +# Svelte + +- [Column Sizing](../framework/svelte/examples/column-sizing) + +# Vue + +- [Column Sizing](../framework/vue/examples/column-sizing) + +# Lit + +- [Column Sizing](../framework/lit/examples/column-sizing) + + ## Column Sizing Guide -The column sizing feature allows you to optionally specify the width of each column including min and max widths. It also allows you and your users the ability to dynamically change the width of all columns at will, eg. by dragging the column headers. +The column sizing feature allows you to optionally specify the width of each column including min and max widths. + +If you want users to dynamically change column widths by dragging column headers, see the [Column Resizing Guide](./column-resizing). ### Column Widths @@ -40,13 +63,15 @@ const columns = [ //... ] -const table = useReactTable({ - //override default column sizing +const table = useTable({ + _features: tableFeatures({ columnSizingFeature }), + _rowModels: {}, defaultColumn: { - size: 200, //starting column size - minSize: 50, //enforced during column resizing - maxSize: 500, //enforced during column resizing + size: 200, // starting column size + minSize: 50, // enforced during column resizing + maxSize: 500, // enforced during column resizing }, + //... }) ``` @@ -63,115 +88,3 @@ As a headless utility, table logic for column sizing is really only a collection - Really any layout mechanism that can interpolate cell widths into a table structure. Each of these approaches has its own tradeoffs and limitations which are usually opinions held by a UI/component library or design system, luckily not you 😉. - -### Column Resizing - -TanStack Table provides built-in column resizing state and APIs that allow you to easily implement column resizing in your table UI with a variety of options for UX and performance. - -#### Enable Column Resizing - -By default, the `column.getCanResize()` API will return `true` by default for all columns, but you can either disable column resizing for all columns with the `enableColumnResizing` table option, or disable column resizing on a per-column basis with the `enableResizing` column option. - -```tsx -const columns = [ - { - accessorKey: 'id', - enableResizing: false, //disable resizing for just this column - size: 200, //starting column size - }, - //... -] -``` - -#### Column Resize Mode - -By default, the column resize mode is set to `"onEnd"`. This means that the `column.getSize()` API will not return the new column size until the user has finished resizing (dragging) the column. Usually a small UI indicator will be displayed while the user is resizing the column. - -In React TanStack Table adapter, where achieving 60 fps column resizing renders can be difficult, depending on the complexity of your table or web page, the `"onEnd"` column resize mode can be a good default option to avoid stuttering or lagging while the user resizes columns. That is not to say that you cannot achieve 60 fps column resizing renders while using TanStack React Table, but you may have to do some extra memoization or other performance optimizations in order to achieve this. - -> Advanced column resizing performance tips will be discussed [down below](#advanced-column-resizing-performance). - -If you want to change the column resize mode to `"onChange"` for immediate column resizing renders, you can do so with the `columnResizeMode` table option. - -```tsx -const table = useReactTable({ - //... - columnResizeMode: 'onChange', //change column resize mode to "onChange" -}) -``` - -#### Column Resize Direction - -By default, TanStack Table assumes that the table markup is laid out in a left-to-right direction. For right-to-left layouts, you may need to change the column resize direction to `"rtl"`. - -```tsx -const table = useReactTable({ - //... - columnResizeDirection: 'rtl', //change column resize direction to "rtl" for certain locales -}) -``` - -#### Connect Column Resizing APIs to UI - -There are a few really handy APIs that you can use to hook up your column resizing drag interactions to your UI. - -##### Column Size APIs - -To apply the size of a column to the column head cells, data cells, or footer cells, you can use the following APIs: - -```ts -header.getSize() -column.getSize() -cell.column.getSize() -``` - -How you apply these size styles to your markup is up to you, but it is pretty common to use either CSS variables or inline styles to apply the column sizes. - -```tsx - -``` - -Though, as discussed in the [advanced column resizing performance section](#advanced-column-resizing-performance), you may want to consider using CSS variables to apply column sizes to your markup. - -##### Column Resize APIs - -TanStack Table provides a pre-built event handler to make your drag interactions easy to implement. These event handlers are just convenience functions that call other internal APIs to update the column sizing state and re-render the table. Use `header.getResizeHandler()` to connect to your column resize drag interactions, for both mouse and touch events. - -```tsx - -``` - -##### Column Resize Indicator with ColumnSizingInfoState - -TanStack Table keeps track of an state object called `columnSizingInfo` that you can use to render a column resize indicator UI. - -```jsx - -``` - -#### Advanced Column Resizing Performance - -If you are creating large or complex tables (and using React 😉), you may find that if you do not add proper memoization to your render logic, your users may experience degraded performance while resizing columns. - -We have created a [performant column resizing example](../../framework/react/examples/column-resizing-performant) that demonstrates how to achieve 60 fps column resizing renders with a complex table that may otherwise have slow renders. It is recommended that you just look at that example to see how it is done, but these are the basic things to keep in mind: - -1. Don't use `column.getSize()` on every header and every data cell. Instead, calculate all column widths once upfront, **memoized**! -2. Memoize your Table Body while resizing is in progress. -3. Use CSS variables to communicate column widths to your table cells. - -If you follow these steps, you should see significant performance improvements while resizing columns. - -If you are not using React, and are using the Svelte, Vue, or Solid adapters instead, you may not need to worry about this as much, but similar principles apply. diff --git a/docs/guide/column-visibility.md b/docs/guide/column-visibility.md index ba7e7e744f..29d733ba4e 100644 --- a/docs/guide/column-visibility.md +++ b/docs/guide/column-visibility.md @@ -6,42 +6,65 @@ title: Column Visibility Guide Want to skip to the implementation? Check out these examples: -- [column-visibility](../../framework/react/examples/column-visibility) -- [column-ordering](../../framework/react/examples/column-ordering) -- [sticky-column-pinning](../../framework/react/examples/column-pinning-sticky) + -### Other Examples +# React -- [SolidJS column-visibility](../../framework/solid/examples/column-visibility) -- [Svelte column-visibility](../../framework/svelte/examples/column-visibility) +- [Column Visibility](../framework/react/examples/column-visibility) -## API +# Preact -[Column Visibility API](../../api/features/column-visibility) +- [Column Visibility](../framework/preact/examples/column-visibility) + +# Solid + +- [Column Visibility](../framework/solid/examples/column-visibility) + +# Svelte + +- [Column Visibility](../framework/svelte/examples/column-visibility) + +# Vue + +- [Column Visibility](../framework/vue/examples/column-visibility) + +# Angular + +- [Column Visibility](../framework/angular/examples/column-visibility) + +# Lit + +- [Column Visibility](../framework/lit/examples/column-visibility) + + ## Column Visibility Guide -The column visibility feature allows table columns to be hidden or shown dynamically. In previous versions of react-table, this feature was a static property on a column, but in v8, there is a dedicated `columnVisibility` state and APIs for managing column visibility dynamically. +The column visibility feature allows table columns to be hidden or shown dynamically. In v9, add `columnVisibilityFeature` to your `_features` to enable this. There is a dedicated `columnVisibility` state and APIs for managing column visibility dynamically. ### Column Visibility State The `columnVisibility` state is a map of column IDs to boolean values. A column will be hidden if its ID is present in the map and the value is `false`. If the column ID is not present in the map, or the value is `true`, the column will be shown. ```jsx +import { useTable, tableFeatures, columnVisibilityFeature } from '@tanstack/react-table' + const [columnVisibility, setColumnVisibility] = useState({ columnId1: true, - columnId2: false, //hide this column by default + columnId2: false, // hide this column by default columnId3: true, -}); +}) -const table = useReactTable({ +const table = useTable({ + _features: tableFeatures({ columnVisibilityFeature }), + _rowModels: {}, //... state: { columnVisibility, //... }, onColumnVisibilityChange: setColumnVisibility, -}); +}) ``` Alternatively, if you don't need to manage the column visibility state outside of the table, you can still set the initial default column visibility state using the `initialState` option. @@ -49,17 +72,19 @@ Alternatively, if you don't need to manage the column visibility state outside o > **Note**: If `columnVisibility` is provided to both `initialState` and `state`, the `state` initialization will take precedence and `initialState` will be ignored. Do not provide `columnVisibility` to both `initialState` and `state`, only one or the other. ```jsx -const table = useReactTable({ +const table = useTable({ + _features: tableFeatures({ columnVisibilityFeature }), + _rowModels: {}, //... initialState: { columnVisibility: { columnId1: true, - columnId2: false, //hide this column by default + columnId2: false, // hide this column by default columnId3: true, }, //... }, -}); +}) ``` ### Disable Hiding Columns diff --git a/docs/guide/columns.md b/docs/guide/columns.md index b2242d6f8b..2bf3b81438 100644 --- a/docs/guide/columns.md +++ b/docs/guide/columns.md @@ -2,13 +2,9 @@ title: Columns Guide --- -## API - -[Column API](../../api/core/column) - ## Columns Guide -> Note: This guide is about the actual `column` objects that are generated within the table instance and NOT about setting up the [column definitions](../column-defs) for your table. +> Note: This guide is about the actual `column` objects that are generated within the table instance and NOT about setting up the [column definitions](./column-defs) for your table. This quick guide will discuss the different ways you can retrieve and interact with `column` objects in TanStack Table. @@ -18,7 +14,7 @@ You can find the `column` objects in many places. They are often attached #### Header and Cell Objects -Before you reach for one of the `table` instance APIs, consider if you actually need to retrieve either [headers](../headers) or [cells](../cells) instead of `columns`. If you are rending out the markup for your table, you will most likely want to reach for the APIs that return headers or cells instead of columns. The column objects themselves are not really meant to render out the headers or cells, but the `header` and `cell` objects will contain references to these `column` objects from which they can derive the necessary information to render their UI. +Before you reach for one of the `table` instance APIs, consider if you actually need to retrieve either [headers](./headers) or [cells](./cells) instead of `columns`. If you are rending out the markup for your table, you will most likely want to reach for the APIs that return headers or cells instead of columns. The column objects themselves are not really meant to render out the headers or cells, but the `header` and `cell` objects will contain references to these `column` objects from which they can derive the necessary information to render their UI. ```js const column = cell.column; // get column from cell @@ -47,7 +43,7 @@ Column objects are not actually meant to be used to render out the table UI dire #### Column IDs -Every column must have a unique `id` defined in their associated [Column Definition](../column-defs). Usually, you define this `id` yourself, or it is derived from the `accessorKey` or `header` properties in the column definition. +Every column must have a unique `id` defined in their associated [Column Definition](./column-defs). Usually, you define this `id` yourself, or it is derived from the `accessorKey` or `header` properties in the column definition. #### ColumnDef @@ -67,6 +63,6 @@ There are dozens of Column APIs that you can use to interact with the table stat ### Column Rendering -Don't necessarily use `column` objects to render `headers` or `cells` directly. Instead, use the [`header`](../headers) and [`cell`](../cells) objects, as discussed above. +Don't necessarily use `column` objects to render `headers` or `cells` directly. Instead, use the [`header`](./headers) and [`cell`](./cells) objects, as discussed above. But if you are just rendering a list of columns somewhere else in your UI for something like a column visibility menu or something similar, you can just map over a columns array and render out the UI as you normally would. diff --git a/docs/guide/custom-features.md b/docs/guide/custom-features.md index 9242209fdd..2fd7dab066 100644 --- a/docs/guide/custom-features.md +++ b/docs/guide/custom-features.md @@ -6,23 +6,35 @@ title: Custom Features Guide Want to skip to the implementation? Check out these examples: -- [custom-features](../../framework/react/examples/custom-features) + + +# React + +- [Custom Plugin](../framework/react/examples/custom-plugin) + +# Preact + +- [Custom Plugin](../framework/preact/examples/custom-plugin) + +# Angular + +- [Custom Plugin](../framework/angular/examples/custom-plugin) + + ## Custom Features Guide -In this guide, we'll cover how to extend TanStack Table with custom features, and along the way, we'll learn more about how the TanStack Table v8 codebase is structured and how it works. +In this guide, we'll cover how to extend TanStack Table with custom features, and along the way, we'll learn more about how the TanStack Table v9 codebase is structured and how it works. ### TanStack Table Strives to be Lean TanStack Table has a core set of features that are built into the library such as sorting, filtering, pagination, etc. We've received a lot of requests and sometimes even some well thought out PRs to add even more features to the library. While we are always open to improving the library, we also want to make sure that TanStack Table remains a lean library that does not include too much bloat and code that is unlikely to be used in most use cases. Not every PR can, or should, be accepted into the core library, even if it does solve a real problem. This can be frustrating to developers where TanStack Table solves 90% of their use case, but they need a little bit more control. -TanStack Table has always been built in a way that allows it to be highly extensible (at least since v7). The `table` instance that is returned from whichever framework adapter that you are using (`useReactTable`, `useVueTable`, etc) is a plain JavaScript object that can have extra properties or APIs added to it. It has always been possible to use composition to add custom logic, state, and APIs to the table instance. Libraries like [Material React Table](https://github.com/KevinVandy/material-react-table/blob/v2/packages/material-react-table/src/hooks/useMRT_TableInstance.ts) have simply created custom wrapper hooks around the `useReactTable` hook to extend the table instance with custom functionality. - -However, starting in version 8.14.0, TanStack Table has exposed a new `_features` table option that allows you to more tightly and cleanly integrate custom code into the table instance in exactly the same way that the built-in table features are already integrated. +TanStack Table has always been built in a way that allows it to be highly extensible (at least since v7). The `table` instance that is returned from whichever framework adapter that you are using (`createTable`, `useTable`, etc) is a plain JavaScript object that can have extra properties or APIs added to it. It has always been possible to use composition to add custom logic, state, and APIs to the table instance. Libraries like [Material React Table](https://github.com/KevinVandy/material-react-table/blob/v2/packages/material-react-table/src/hooks/useMRT_TableInstance.ts) have simply created custom wrapper hooks around the `useTable` hook to extend the table instance with custom functionality. -> TanStack Table v8.14.0 introduced a new `_features` option that allows you to add custom features to the table instance. +In v9, TanStack Table uses the `_features` option (via `tableFeatures()`) to declare which features your table uses. This enables tree-shaking—you only bundle the code for the features you need. You can add custom features to the table instance in exactly the same way as the built-in features. -With this new tighter integration, you can easily add more complex custom features to your tables, and possibly even package them up and share them with the community. We'll see how this evolves over time. In a future v9 release, we may even lower the bundle size of TanStack Table by making all features opt-in, but that is still being explored. +> In v9, features are opt-in. Use `tableFeatures({ ... })` to declare which features your table uses, including custom features. ### How TanStack Table Features Work @@ -31,22 +43,16 @@ TanStack Table's source code is arguably somewhat simple (at least we think so). All of the functionality of a feature object can be described with the `TableFeature` type that is exported from TanStack Table. This type is a TypeScript interface that describes the shape of a feature object needed to create a feature. ```ts -export interface TableFeature { - createCell?: ( - cell: Cell, - column: Column, - row: Row, - table: Table - ) => void - createColumn?: (column: Column, table: Table) => void - createHeader?: (header: Header, table: Table) => void - createRow?: (row: Row, table: Table) => void - createTable?: (table: Table) => void - getDefaultColumnDef?: () => Partial> - getDefaultOptions?: ( - table: Table - ) => Partial> - getInitialState?: (initialState?: InitialTableState) => Partial +export interface TableFeature { + assignCellPrototype?: AssignCellPrototype + assignColumnPrototype?: AssignColumnPrototype + assignHeaderPrototype?: AssignHeaderPrototype + assignRowPrototype?: AssignRowPrototype + constructTableAPIs?: ConstructTableAPIs + getDefaultColumnDef?: GetDefaultColumnDef + getDefaultTableOptions?: GetDefaultTableOptions + getInitialState?: GetInitialState + initRowInstanceData?: InitRowInstanceData } ``` @@ -56,59 +62,59 @@ This might be a bit confusing, so let's break down what each of these methods do
-##### getDefaultOptions +##### getDefaultTableOptions -The `getDefaultOptions` method in a table feature is responsible for setting the default table options for that feature. For example, in the [Column Sizing](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/ColumnSizing.ts) feature, the `getDefaultOptions` method sets the default `columnResizeMode` option with a default value of `"onEnd"`. +The `getDefaultTableOptions` method in a table feature is responsible for setting the default table options for that feature. For example, in the [Column Sizing](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.ts) feature, the `getDefaultTableOptions` method sets the default `columnResizeMode` option with a default value of `"onEnd"`.
##### getDefaultColumnDef -The `getDefaultColumnDef` method in a table feature is responsible for setting the default column options for that feature. For example, in the [Sorting](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/RowSorting.ts) feature, the `getDefaultColumnDef` method sets the default `sortUndefined` column option with a default value of `1`. +The `getDefaultColumnDef` method in a table feature is responsible for setting the default column options for that feature. For example, in the [Sorting](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.ts) feature, the `getDefaultColumnDef` method sets the default `sortUndefined` column option with a default value of `1`.
##### getInitialState -The `getInitialState` method in a table feature is responsible for setting the default state for that feature. For example, in the [Pagination](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/RowPagination.ts) feature, the `getInitialState` method sets the default `pageSize` state with a value of `10` and the default `pageIndex` state with a value of `0`. +The `getInitialState` method in a table feature is responsible for setting the default state for that feature. For example, in the [Pagination](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/rowPaginationFeature.ts) feature, the `getInitialState` method sets the default `pageSize` state with a value of `10` and the default `pageIndex` state with a value of `0`. #### API Creators
-##### createTable +##### constructTableAPIs -The `createTable` method in a table feature is responsible for adding methods to the `table` instance. For example, in the [Row Selection](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/RowSelection.ts) feature, the `createTable` method adds many table instance API methods such as `toggleAllRowsSelected`, `getIsAllRowsSelected`, `getIsSomeRowsSelected`, etc. So then, when you call `table.toggleAllRowsSelected()`, you are calling a method that was added to the table instance by the `RowSelection` feature. +The `constructTableAPIs` method in a table feature is responsible for adding methods to the `table` instance. For example, in the [Row Selection](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.ts) feature, the `constructTableAPIs` method adds many table instance API methods such as `toggleAllRowsSelected`, `getIsAllRowsSelected`, `getIsSomeRowsSelected`, etc. So then, when you call `table.toggleAllRowsSelected()`, you are calling a method that was added to the table instance by the `rowSelectionFeature` feature.
-##### createHeader +##### assignHeaderPrototype -The `createHeader` method in a table feature is responsible for adding methods to the `header` instance. For example, in the [Column Sizing](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/ColumnSizing.ts) feature, the `createHeader` method adds many header instance API methods such as `getStart`, and many others. So then, when you call `header.getStart()`, you are calling a method that was added to the header instance by the `ColumnSizing` feature. +The `assignHeaderPrototype` method in a table feature is responsible for adding methods to the shared `header` prototype. For example, the [Column Sizing](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.ts) feature adds header instance API methods such as `getStart`. So then, when you call `header.getStart()`, you are calling a method that was added by the column sizing feature.
-##### createColumn +##### assignColumnPrototype -The `createColumn` method in a table feature is responsible for adding methods to the `column` instance. For example, in the [Sorting](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/RowSorting.ts) feature, the `createColumn` method adds many column instance API methods such as `getNextSortingOrder`, `toggleSorting`, etc. So then, when you call `column.toggleSorting()`, you are calling a method that was added to the column instance by the `RowSorting` feature. +The `assignColumnPrototype` method in a table feature is responsible for adding methods to the shared `column` prototype. For example, the [Sorting](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.ts) feature adds column instance API methods such as `getNextSortingOrder`, `toggleSorting`, etc. So then, when you call `column.toggleSorting()`, you are calling a method that was added by the row sorting feature.
-##### createRow +##### assignRowPrototype and initRowInstanceData -The `createRow` method in a table feature is responsible for adding methods to the `row` instance. For example, in the [Row Selection](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/RowSelection.ts) feature, the `createRow` method adds many row instance API methods such as `toggleSelected`, `getIsSelected`, etc. So then, when you call `row.toggleSelected()`, you are calling a method that was added to the row instance by the `RowSelection` feature. +The `assignRowPrototype` method in a table feature is responsible for adding methods to the shared `row` prototype. The `initRowInstanceData` method is available for per-row instance data or caches that cannot live on the shared prototype. For example, the [Row Selection](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.ts) feature adds row instance API methods such as `toggleSelected` and `getIsSelected`.
-##### createCell +##### assignCellPrototype -The `createCell` method in a table feature is responsible for adding methods to the `cell` instance. For example, in the [Column Grouping](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/ColumnGrouping.ts) feature, the `createCell` method adds many cell instance API methods such as `getIsGrouped`, `getIsAggregated`, etc. So then, when you call `cell.getIsGrouped()`, you are calling a method that was added to the cell instance by the `ColumnGrouping` feature. +The `assignCellPrototype` method in a table feature is responsible for adding methods to the shared `cell` prototype. For example, the [Column Grouping](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.ts) feature adds cell instance API methods such as `getIsGrouped` and `getIsAggregated`. ### Adding a Custom Feature Let's walk through making a custom table feature for a hypothetical use case. Let's say we want to add a feature to the table instance that allows the user to change the "density" (padding of cells) of the table. -Check out the full [custom-features](../../framework/react/examples/custom-features) example to see the full implementation, but here's an in-depth look at the steps to create a custom feature. +Check out the full [custom-plugin](../framework/react/examples/custom-plugin) example to see the full implementation, but here's an in-depth look at the steps to create a custom feature. #### Step 1: Set up TypeScript Types @@ -119,56 +125,42 @@ These types are following the naming convention used internally within TanStack ```ts // 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 } // Define types for our new feature's table APIs -export interface DensityInstance { +export interface Table_Density { setDensity: (updater: Updater) => void toggleDensity: (value?: DensityState) => void } + +interface DensityPluginConstructors { + Table: Table_Density + TableOptions: TableOptions_Density + TableState: TableState_Density +} ``` -#### Step 2: Use Declaration Merging to Add New Types to TanStack Table +#### Step 2: Add the Feature to the TableFeatures Interface -We can tell TypeScript to modify the exported types from TanStack Table to include our new feature's types. This is called "declaration merging" and it's a powerful feature of TypeScript. This way, we should not have to use any TypeScript hacks such as `as unknown as CustomTable` or `// @ts-ignore` in our new feature's code or in our application code. +TanStack Table uses the keys passed to `tableFeatures({ ... })` to infer which feature state, options, and APIs exist on a table. To make a custom feature key type-safe, add it to the exported `Plugins` interface with declaration merging. ```ts -// Use declaration merging to add our new feature APIs and state types to TanStack Table's existing types. -declare module '@tanstack/react-table' { // or whatever framework adapter you are using - //merge our new feature's state with the existing table state - interface TableState extends DensityTableState {} - //merge our new feature's options with the existing table options - interface TableOptionsResolved - extends DensityOptions {} - //merge our new feature's instance APIs with the existing table instance APIs - interface Table extends DensityInstance {} - // if you need to add cell instance APIs... - // interface Cell extends DensityCell - // if you need to add row instance APIs... - // interface Row extends DensityRow - // if you need to add column instance APIs... - // interface Column extends DensityColumn - // if you need to add header instance APIs... - // interface Header extends DensityHeader - - // Note: declaration merging on `ColumnDef` is not possible because it is a complex type, not an interface. - // But you can still use declaration merging on `ColumnDef.meta` +declare module '@tanstack/react-table' { + interface Plugins { + densityPlugin?: TableFeature + } } ``` -Once we do this correctly, we should have no TypeScript errors when we try to both create our new feature's code and use it in our application. - -##### Caveats of Using Declaration Merging - -One caveat of using declaration merging is that it will affect the TanStack Table types for every table across your codebase. This is not a problem if you plan on loading the same feature set for every table in your application, but it could be a problem if some of your tables load extra features and some do not. Alternatively, you can just make a bunch of custom types that extend off of the TanStack Table types with your new features added. This is what [Material React Table](https://github.com/KevinVandy/material-react-table/blob/v2/packages/material-react-table/src/types.ts) does in order to avoid affecting the types of vanilla TanStack Table tables, but it's a bit more tedious, and requires a lot of type casting at certain points. +Once the feature is registered this way, TypeScript can infer the feature's state, options, and APIs only on tables whose `_features` include `densityPlugin`. #### Step 3: Create the Feature Object @@ -177,64 +169,64 @@ With all of that TypeScript setup out of the way, we can now create the feature Use the `TableFeature` type to ensure that you are creating the feature object correctly. If the TypeScript types are set up correctly, you should have no TypeScript errors when you create the feature object with the new state, options, and instance APIs. ```ts -export const DensityFeature: TableFeature = { //Use the TableFeature type!! +export const densityPlugin: TableFeature = { // define the new feature's initial state - getInitialState: (state): DensityTableState => { + getInitialState: (initialState) => { return { density: 'md', - ...state, + ...initialState, // must come last } }, // define the new feature's default options - getDefaultOptions: ( - table: Table - ): DensityOptions => { + getDefaultTableOptions: (table) => { return { enableDensity: true, onDensityChange: makeStateUpdater('density', table), - } as DensityOptions + } }, // if you need to add a default column definition... - // getDefaultColumnDef: (): Partial> => { - // return { meta: {} } //use meta instead of directly adding to the columnDef to avoid typescript stuff that's hard to workaround - // }, + // getDefaultColumnDef: () => {}, // define the new feature's table instance methods - createTable: (table: Table): void => { - table.setDensity = updater => { - const safeUpdater: Updater = old => { - let newState = functionalUpdate(updater, old) + constructTableAPIs: (table) => { + table.setDensity = (updater) => { + const safeUpdater: Updater = (old) => { + const newState = functionalUpdate(updater, old) return newState } return table.options.onDensityChange?.(safeUpdater) } - table.toggleDensity = value => { - table.setDensity(old => { + table.toggleDensity = (value) => { + table.setDensity?.((old) => { if (value) return value - return old === 'lg' ? 'md' : old === 'md' ? 'sm' : 'lg' //cycle through the 3 options + return old === 'lg' ? 'md' : old === 'md' ? 'sm' : 'lg' }) } }, // if you need to add row instance APIs... - // createRow: (row, table): void => {}, + // assignRowPrototype: (prototype, table) => {}, + // initRowInstanceData: (row) => {}, // if you need to add cell instance APIs... - // createCell: (cell, column, row, table): void => {}, + // assignCellPrototype: (prototype, table) => {}, // if you need to add column instance APIs... - // createColumn: (column, table): void => {}, + // assignColumnPrototype: (prototype, table) => {}, // if you need to add header instance APIs... - // createHeader: (header, table): void => {}, + // assignHeaderPrototype: (prototype, table) => {}, } ``` #### Step 4: Add the Feature to the Table -Now that we have our feature object, we can add it to the table instance by passing it to the `_features` option when we create the table instance. +Now that we have our feature object, we can add it to the table instance by including it in the `tableFeatures()` call and passing the result to the `_features` option when we create the table instance. ```ts -const table = useReactTable({ - _features: [DensityFeature], //pass the new feature to merge with all of the built-in features under the hood +const _features = tableFeatures({ densityPlugin }) + +const table = useTable({ + _features, + _rowModels: {}, columns, data, //.. @@ -246,18 +238,21 @@ const table = useReactTable({ Now that the feature is added to the table instance, you can use the new instance APIs options, and state in your application. ```tsx -const table = useReactTable({ - _features: [DensityFeature], //pass our custom feature to the table to be instantiated upon creation +const _features = tableFeatures({ densityPlugin }) + +const table = useTable({ + _features, + _rowModels: {}, columns, data, //... state: { - density, //passing the density state to the table, TS is still happy :) + density, }, - onDensityChange: setDensity, //using the new onDensityChange option, TS is still happy :) + onDensityChange: setDensity, }) //... -const { density } = table.getState() +const density = table.atoms.density.get() return(
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} + {flexRender(cell.column.columnDef.cell, cell.getContext())}
- + {flexRender(cell.column.columnDef.cell, cell.getContext())}
` or similar cell element in your UI. There are a few properties and methods on `header` objects that you can use to interact with the table state and extract cell values from the table based on the state of the table. +Header objects are similar to [Cell](./cells) objects, but meant for the `
` or similar cell element in your UI. There are a few properties and methods on `header` objects that you can use to interact with the table state and extract cell values from the table based on the state of the table. #### Header IDs -Every header object has an `id` property that makes it unique within the table instance. Usually you only need this `id` as a unique identifier for React keys or if you are following the [performant column resizing example](../../framework/react/examples/column-resizing-performant). +Every header object has an `id` property that makes it unique within the table instance. Usually you only need this `id` as a unique identifier for React keys or if you are following the [performant column resizing example](../framework/react/examples/column-resizing-performant). For simple headers with no advanced nested or grouped headers logic, the `header.id` will be the same as it's parent `column.id`. However, if the header is part group column or a placeholder cell, it will have a more complicated id that is constructed from the header family, depth/header row index, column id, and header group id. @@ -65,11 +61,11 @@ There are a few properties on `header` objects that are only useful if the heade #### Header Parent Objects -Every header stores a reference to its parent [column](../columns) object and its parent [header group](../header-groups) object. +Every header stores a reference to its parent [column](./columns) object and its parent [header group](./header-groups) object. ### More Header APIs -Headers have a few more useful APIs attached to them that are useful for interacting with the table state. Most of them relate to the Column sizing and resizing features. See the [Column Resizing Guide](../column-resizing) for more information. +Headers have a few more useful APIs attached to them that are useful for interacting with the table state. Most of them relate to the column sizing and resizing features. See the [Column Sizing Guide](./column-sizing) and [Column Resizing Guide](./column-resizing) for more information. ### Header Rendering @@ -81,4 +77,4 @@ Since the `header` column option you defined can be either a string, jsx, or a f {/* Handles all possible header column def scenarios for `header` */} {flexRender(header.column.columnDef.header, header.getContext())} {cell.render('Header')} // [!code ++] -+ {flexRender( // [!code ++] -+ header.column.columnDef.header, // [!code ++] -+ header.getContext() // [!code ++] -+ )} // [!code ++] -+ {cell.render('Cell')} // [!code ++] -+ {flexRender( // [!code ++] -+ cell.column.columnDef.cell, // [!code ++] -+ cell.getContext() // [!code ++] -+ )} // [!code ++] -+
` element. Instead, we're referring to the core table object that contains the table state and APIs. The `table` instance is created by calling your adapter's `createTable` function (e.g. `useReactTable`, `useVueTable`, `createSolidTable`, `createSvelteTable`, `createAngularTable`, `useQwikTable`). +TanStack Table is a headless UI library. When we talk about the `table` or "table instance", we're not talking about a literal `
` element. Instead, we're referring to the core table object that coordinates table state and APIs. The `table` instance is created by calling your adapter's table creation function (e.g. `useTable`, `createTable`, `injectTable`, or `constructTable`). -The `table` instance that is returned from the `createTable` function (from the framework adapter) is the main object that you will interact with to read and mutate the table state. It is the one place where everything happens in TanStack Table. When you get to the point where you are rendering your UI, you will use APIs from this `table` instance. +The `table` instance that is returned from the `_createTable` function (from the framework adapter) is the main object that you will interact with to read and mutate the table state. It is the one place where everything happens in TanStack Table. When you get to the point where you are rendering your UI, you will use APIs from this `table` instance. ### Creating a Table Instance -To create a table instance, 3 `options` are required: `columns`, `data`, and a `getCoreRowModel` implementation. There are dozens of other table options to configure features and behavior, but these 3 are required. +To create a table instance, 3 `options` are required: `columns`, `data`, and `_features`. The `_features` option declares which table features your table uses (enabling tree-shaking—you only bundle what you use). The core row model is included automatically; add additional row models via `_rowModels` when you need filtering, sorting, pagination, etc. There are dozens of other table options to configure features and behavior. #### Defining Data -Define your data as an array of objects with a stable reference. `data` can come from anywhere like an API response or defined statically in your code, but it must have a stable reference to prevent infinite re-renders. If using TypeScript, the type that you give your data will be used as a `TData` generic. See the [Data Guide](../data) for more info. +Define your data as an array of objects with a stable reference. `data` can come from anywhere like an API response or defined statically in your code, but it must have a stable reference to prevent infinite re-renders. If using TypeScript, the type that you give your data will be used as a `TData` generic. See the [Data Guide](./data) for more info. #### Defining Columns -Column definitions are covered in detail in the previous section in the [Column Def Guide](../column-defs). We'll note here, however, that when you define the type of your columns, you should use the same `TData` type that you used for your data. +Column definitions are covered in detail in the previous section in the [Column Def Guide](./column-defs). We'll note here, however, that when you define the type of your columns, you should use the same `TData` type that you used for your data. ```ts -const columns: ColumnDef[] = [] //Pass User type as the generic TData type +const _features = tableFeatures({}) // Define which features your table uses +const columns: ColumnDef[] = [] // Pass User type as TData; use typeof _features for TFeatures //or -const columnHelper = createColumnHelper() //Pass User type as the generic TData type +const columnHelper = createColumnHelper() // Pass both TFeatures and TData in v9 ``` -The column definitions are where we will tell TanStack Table how each column should access and/or transform row data with either an `accessorKey` or `accessorFn`. See the [Column Def Guide](../column-defs#creating-accessor-columns) for more info. +The column definitions are where we will tell TanStack Table how each column should access and/or transform row data with either an `accessorKey` or `accessorFn`. See the [Column Def Guide](./column-defs#creating-accessor-columns) for more info. -#### Passing in Row Models +#### Defining Features and Row Models -This is explained in much more detail in the [Row Models Guide](../row-models), but for now, just import the `getCoreRowModel` function from TanStack Table and pass it in as a table option. Depending on the features you plan to use, you may need to pass in additional row models later. +This is explained in much more detail in the [Row Models Guide](./row-models). In v9, you declare which features your table uses via `_features` (using `tableFeatures()`), and which row models to apply via `_rowModels`. The core row model is always included automatically. For a basic table with no filtering, sorting, or pagination, pass an empty features object and empty row models: ```ts -import { getCoreRowModel } from '@tanstack/[framework]-table' +import { tableFeatures } from '@tanstack/[framework]-table' + +const _features = tableFeatures({}) // Core features only; add columnFilteringFeature, rowSortingFeature, etc. as needed -const table = createTable({ columns, data, getCoreRowModel: getCoreRowModel() }) +const table = _createTable({ + _features, + _rowModels: {}, // Core row model is automatic; add filteredRowModel, sortedRowModel, etc. as needed + columns, + data, +}) ``` #### Initializing the Table Instance -With our `columns`, `data`, and `getCoreRowModel` defined, we can now create our basic table instance, along side any other table options that we want to pass in. +With our `_features`, `columns`, `data`, and `_rowModels` defined, we can now create our basic table instance, along side any other table options that we want to pass in. + +> **Framework note:** This guide uses React examples. Other frameworks (Angular, Lit, Solid, Svelte, Vue) use `createTable`, `injectTable`, or similar with the same options. ```ts //vanilla js -const table = createTable({ columns, data, getCoreRowModel: getCoreRowModel() }) +const table = _createTable({ _features, _rowModels: {}, columns, data }) //angular -this.table = createAngularTable({ columns: this.columns, data: this.data(), getCoreRowModel: getCoreRowModel() }) +this.table = injectTable({ _features, _rowModels: {}, columns: this.columns, data: this.data() }) //lit -const table = this.tableController.table({ columns, data, getCoreRowModel: getCoreRowModel() }) - -//qwik -const table = useQwikTable({ columns, data, getCoreRowModel: getCoreRowModel() }) +const table = this.tableController.table({ _features, _rowModels: {}, columns, data }) //react -const table = useReactTable({ columns, data, getCoreRowModel: getCoreRowModel() }) +const table = useTable({ _features, _rowModels: {}, columns, data }) //solid -const table = createSolidTable({ columns, get data() { return data() }, getCoreRowModel: getCoreRowModel() }) +const table = createTable({ _features, _rowModels: {}, columns, get data() { return data() } }) //svelte -const table = createSvelteTable({ columns, data, getCoreRowModel: getCoreRowModel() }) +const table = createTable({ _features, _rowModels: {}, columns, data }) //vue -const table = useVueTable({ columns, data, getCoreRowModel: getCoreRowModel() }) +const table = useTable({ _features, _rowModels: {}, columns, data }) ``` So what's in the `table` instance? Let's take a look at what interactions we can have with the table instance. ### Table State -The table instance contains all of the table state, which can be accessed via the `table.getState()` API. Each table feature registers various states in the table state. For example, the row selection feature registers `rowSelection` state, the pagination feature registers `pagination` state, etc. +The table instance contains the state slices registered by its `_features`. Each feature registers the state it owns. For example, the row selection feature registers `rowSelection` state, and the pagination feature registers `pagination` state. In v9, those slices are backed by TanStack Store atoms: -Each feature will also have corresponding state setter APIs and state resetter APIs on the table instance. For example, the row selection feature will have a `setRowSelection` API and a `resetRowSelection`. +- `table.baseAtoms` are the internal writable atoms. +- `table.atoms` are the public readonly atoms for each slice. +- `table.store` is the readonly flat store derived from `table.atoms`. ```ts -table.getState().rowSelection //read the row selection state +table.atoms.rowSelection.get() // read the current row selection state +table.store.state.rowSelection // read the current table state snapshot table.setRowSelection((old) => ({...old})) //set the row selection state table.resetRowSelection() //reset the row selection state ``` -This is covered in more detail in the [Table State Guides](../../framework/react/guide/table-state) +Direct reads like `table.atoms.rowSelection.get()` and `table.store.state.rowSelection` are current values. Framework adapters add their own reactive state access APIs where needed. This is covered in more detail in the [Table State Guides](../framework/react/guide/table-state). ### Table APIs There are dozens of table APIs created by each feature to help you either read or mutate the table state in different ways. -API reference docs for the core table instance and all other feature APIs can be found throughout the API docs. - -For example, you can find the core table instance API docs here: [Table API](../../api/core/table#table-api) +API reference docs for the core table instance and all other feature APIs can be found throughout the [Reference Docs](../reference/index). ### Table Row Models -There is a special set of table instance APIs for reading rows out of the table instance called row models. TanStack Table has advanced features where the rows that are generated may be very different than the array of `data` that you originally passed in. To learn more about the different row models that you can pass in as a table option, see the [Row Models Guide](../row-models). +There is a special set of table instance APIs for reading rows out of the table instance called row models. TanStack Table has advanced features where the rows that are generated may be very different than the array of `data` that you originally passed in. To learn more about the different row models that you can pass in as a table option, see the [Row Models Guide](./row-models). diff --git a/docs/guide/virtualization.md b/docs/guide/virtualization.md index 3298979908..b2f08fba8b 100644 --- a/docs/guide/virtualization.md +++ b/docs/guide/virtualization.md @@ -6,14 +6,41 @@ title: Virtualization Guide Want to skip to the implementation? Check out these examples: -- [virtualized-columns](../../framework/react/examples/virtualized-columns) -- [virtualized-rows (dynamic row height)](../../framework/react/examples/virtualized-rows) -- [virtualized-rows (fixed row height)](../../../../../virtual/v3/docs/framework/react/examples/table) -- [virtualized-infinite-scrolling](../../framework/react/examples/virtualized-infinite-scrolling) + -## API +# React -[TanStack Virtual Virtualizer API](../../../../../virtual/v3/docs/api/virtualizer) +- [Virtualized Columns](../framework/react/examples/virtualized-columns) +- [Virtualized Rows](../framework/react/examples/virtualized-rows) +- [Virtualized Infinite Scrolling](../framework/react/examples/virtualized-infinite-scrolling) + +# Solid + +- [Virtualized Columns](../framework/solid/examples/virtualized-columns) +- [Virtualized Rows](../framework/solid/examples/virtualized-rows) +- [Virtualized Infinite Scrolling](../framework/solid/examples/virtualized-infinite-scrolling) + +# Svelte + +- [Virtualized Columns](../framework/svelte/examples/virtualized-columns) +- [Virtualized Rows](../framework/svelte/examples/virtualized-rows) +- [Virtualized Infinite Scrolling](../framework/svelte/examples/virtualized-infinite-scrolling) + +# Vue + +- [Virtualized Columns](../framework/vue/examples/virtualized-columns) +- [Virtualized Rows](../framework/vue/examples/virtualized-rows) +- [Virtualized Infinite Scrolling](../framework/vue/examples/virtualized-infinite-scrolling) + +# Lit + +- [Virtualized Columns](../framework/lit/examples/virtualized-columns) +- [Virtualized Rows](../framework/lit/examples/virtualized-rows) +- [Virtualized Infinite Scrolling](../framework/lit/examples/virtualized-infinite-scrolling) + + + +Also see the [TanStack Virtual table example](https://tanstack.com/virtual/latest/docs/framework/react/examples/table). ## Virtualization Guide diff --git a/docs/installation.md b/docs/installation.md index 27c2e9a5ea..ef66530137 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -4,78 +4,48 @@ title: Installation Before we dig in to the API, let's get you set up! -Install your table adapter as a dependency using your favorite npm package manager. +Install your table adapter as a dependency using your preferred package manager: -_Only install ONE of the following packages:_ + -## React Table +react: @tanstack/react-table +vue: @tanstack/vue-table +solid: @tanstack/solid-table +svelte: @tanstack/svelte-table +angular: @tanstack/angular-table +lit: @tanstack/lit-table -```bash -npm install @tanstack/react-table -``` + -The `@tanstack/react-table` package works with React 16.8, React 17, React 18, and React 19. + -> NOTE: Even though the react adapter works with React 19, it may not work with the new React Compiler that's coming out along-side React 19. This may be fixed in future TanStack Table updates. +# React -## Vue Table +The `@tanstack/react-table` package works with React 16.8, React 17, React 18, and React 19. -```bash -npm install @tanstack/vue-table -``` +# Vue The `@tanstack/vue-table` package works with Vue 3. -## Solid Table - -```bash -npm install @tanstack/solid-table -``` +# Solid -The `@tanstack/solid-table` package works with Solid-JS 1 +The `@tanstack/solid-table` package works with Solid-JS 1. -## Svelte Table - -```bash -npm install @tanstack/svelte-table -``` +# Svelte The `@tanstack/svelte-table` package works with Svelte 3 and Svelte 4. -> NOTE: There is not a built-in Svelte 5 adapter yet, but you can still use TanStack Table with Svelte 5 by installing the `@tanstack/table-core` package and using a custom adapter from the community. See this [PR](https://github.com/TanStack/table/pull/5403) for inspiration. - -## Qwik Table - -```bash -npm install @tanstack/qwik-table -``` +> [!NOTE] +> There is not a built-in Svelte 5 adapter yet, but you can still use TanStack Table with Svelte 5 by installing the `@tanstack/table-core` package and using a custom adapter from the community. See this [PR](https://github.com/TanStack/table/pull/5403) for inspiration. -The `@tanstack/qwik-table` package works with Qwik 1. - -> NOTE: There will be a "breaking change" release in the near future to support Qwik 2. This will be released as a minor version bump, but will be documented. Qwik 2 itself will have no breaking changes, but its name on the npm registry will change, and require different peer dependencies. - -> NOTE: The current qwik adapter only works with CSR. More improvements may not be available until a future table version. - -## Angular Table - -```bash -npm install @tanstack/angular-table -``` +# Angular The `@tanstack/angular-table` package works with Angular 17. The Angular adapter uses a new Angular Signal implementation. -## Lit Table - -```bash -npm install @tanstack/lit-table -``` +# Lit The `@tanstack/lit-table` package works with Lit 3. -## Table Core (no framework) - -```bash -npm install @tanstack/table-core -``` + Don't see your favorite framework (or favorite version of your framework) listed? You can always just use the `@tanstack/table-core` package and build your own adapter in your own codebase. Usually, only a thin wrapper is needed to manage state and rendering for your specific framework. Browse the [source code](https://github.com/TanStack/table/tree/main/packages) of all of the other adapters to see how they work. diff --git a/docs/introduction.md b/docs/introduction.md index 5b320f57e7..86fc18df01 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -2,7 +2,7 @@ title: Introduction --- -TanStack Table is a **Headless UI** library for building powerful tables & datagrids for TS/JS, React, Vue, Solid, Qwik, and Svelte. +TanStack Table is a **Headless UI** library for building powerful tables & datagrids for TS/JS, React, Vue, Solid, and Svelte. ## What is "headless" UI? diff --git a/docs/overview.md b/docs/overview.md index 4a01e92d14..e3026346e9 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -12,52 +12,52 @@ If you use TypeScript, you will get top-notch type safety and editor autocomplet ## Headless -As it was mentioned extensively in the [Intro](../introduction) section, TanStack Table is **headless**. This means that it doesn't render any DOM elements, and instead relies on you, the UI/UX developer to provide the table's markup and styles. This is a great way to build a table that can be used in any UI framework, including React, Vue, Solid, Svelte, Qwik, Angular, and even JS-to-native platforms like React Native! +As it was mentioned extensively in the [Intro](./introduction) section, TanStack Table is **headless**. This means that it doesn't render any DOM elements, and instead relies on you, the UI/UX developer to provide the table's markup and styles. This is a great way to build a table that can be used in any UI framework, including React, Vue, Solid, Svelte, Angular, and even JS-to-native platforms like React Native! ## Agnostic Since TanStack Table is headless and runs on a vanilla JavaScript core, it is agnostic in a couple of ways: -1. TanStack Table is **Framework Agnostic**, which means you can use it with any JavaScript framework (or library) that you want. TanStack Table provides ready-to-use adapters for React, Vue, Solid, Svelte, and Qwik out of the box, but you can create your own adapter if you need to. +1. TanStack Table is **Framework Agnostic**, which means you can use it with any JavaScript framework (or library) that you want. TanStack Table provides ready-to-use adapters for React, Vue, Solid, Svelte out of the box, but you can create your own adapter if you need to. 2. TanStack Table is **CSS / Component Library Agnostic**, which means that you can use TanStack Table with whatever CSS strategy or component library you want. TanStack Table itself does not render any table markup or styles. You bring your own! Want to use Tailwind or ShadCN? No problem! Want to use Material UI or Bootstrap? No problem! Have your own custom design system? TanStack Table was made for you! ## Core Objects and Types The table core uses the following abstractions, commonly exposed by adapters: -- [Data](../guide/data) - The core data array you provide the table -- [Column Defs](../guide/column-defs): Objects used to configure a column and its data model, display templates, and more -- [Table Instance](../guide/tables): The core table object containing both state and API -- [Row Models](../guide/row-models): How the `data` array is transformed into useful rows depending on the features you are using -- [Rows](../guide/rows): Each row mirrors its respective row data and provides row-specific APIs -- [Cells](../guide/cells): Each cell mirrors its respective row-column intersection and provides cell-specific APIs -- [Header Groups](../guide/header-groups): Header groups are computed slices of nested header levels, each containing a group of headers -- [Headers](../guide/headers): Each header is either directly associated with or derived from its column def and provides header-specific APIs -- [Columns](../guide/columns): Each column mirrors its respective column def and also provides column-specific APIs +- [Data](./guide/data) - The core data array you provide the table +- [Column Defs](./guide/column-defs): Objects used to configure a column and its data model, display templates, and more +- [Table Instance](./guide/tables): The core table object containing both state and API +- [Row Models](./guide/row-models): How the `data` array is transformed into useful rows depending on the features you are using +- [Rows](./guide/rows): Each row mirrors its respective row data and provides row-specific APIs +- [Cells](./guide/cells): Each cell mirrors its respective row-column intersection and provides cell-specific APIs +- [Header Groups](./guide/header-groups): Header groups are computed slices of nested header levels, each containing a group of headers +- [Headers](./guide/headers): Each header is either directly associated with or derived from its column def and provides header-specific APIs +- [Columns](./guide/columns): Each column mirrors its respective column def and also provides column-specific APIs ## Features TanStack Table will help you build just about any type of table you can imagine. It has built-in state and APIs for the following features: -- [Column Faceting](../guide/column-faceting) - List unique lists of column values or min/max values for a column -- [Column Filtering](../guide/column-filtering) - Filter rows based on search values for a column -- [Column Grouping](../guide/grouping) - Group columns together, run aggregations, and more -- [Column Ordering](../guide/column-ordering) - Dynamically change the order of columns -- [Column Pinning](../guide/column-pinning) - Pin (Freeze) columns to the left or right of the table -- [Column Sizing](../guide/column-sizing) - Dynamically change the size of columns (column resizing handles) -- [Column Visibility](../guide/column-visibility) - Hide/show columns -- [Global Faceting](../guide/global-faceting) - List unique lists of column values or min/max values for the entire table -- [Global Filtering](../guide/global-filtering) - Filter rows based on search values for the entire table -- [Row Expanding](../guide/expanding) - Expand/collapse rows (sub-rows) -- [Row Pagination](../guide/pagination) - Paginate rows -- [Row Pinning](../guide/row-pinning) - Pin (Freeze) rows to the top or bottom of the table -- [Row Selection](../guide/row-selection) - Select/deselect rows (checkboxes) -- [Row Sorting](../guide/sorting) - Sort rows by column values +- [Column Faceting](./guide/column-faceting) - List unique lists of column values or min/max values for a column +- [Column Filtering](./guide/column-filtering) - Filter rows based on search values for a column +- [Column Grouping](./guide/grouping) - Group columns together, run aggregations, and more +- [Column Ordering](./guide/column-ordering) - Dynamically change the order of columns +- [Column Pinning](./guide/column-pinning) - Pin (Freeze) columns to the left or right of the table +- [Column Sizing](./guide/column-sizing) - Dynamically change the size of columns (column resizing handles) +- [Column Visibility](./guide/column-visibility) - Hide/show columns +- [Global Faceting](./guide/global-faceting) - List unique lists of column values or min/max values for the entire table +- [Global Filtering](./guide/global-filtering) - Filter rows based on search values for the entire table +- [Row Expanding](./guide/expanding) - Expand/collapse rows (sub-rows) +- [Row Pagination](./guide/pagination) - Paginate rows +- [Row Pinning](./guide/row-pinning) - Pin (Freeze) rows to the top or bottom of the table +- [Row Selection](./guide/row-selection) - Select/deselect rows (checkboxes) +- [Row Sorting](./guide/sorting) - Sort rows by column values These are just some of the capabilities that you can build with TanStack Table. There are many more features that are possible with TanStack Table that you can add along-side the built-in features. -[Virtualization](../guide/virtualization) is an example of a feature that is not built-in to TanStack Table, but can be achieved by using another library (like [TanStack Virtual](https://tanstack.com/virtual/v3)) and adding it along-side your other table rendering logic. +[Virtualization](./guide/virtualization) is an example of a feature that is not built-in to TanStack Table, but can be achieved by using another library (like [TanStack Virtual](https://tanstack.com/virtual/v3)) and adding it along-side your other table rendering logic. -TanStack Table also supports [Custom Features](../guide/custom-features) (plugins) that you can use to modify the table instance to add your own custom logic to the table in a more integrated way. +TanStack Table also supports [Custom Features](./guide/custom-features) (plugins) that you can use to modify the table instance to add your own custom logic to the table in a more integrated way. And of course, you can just write your own state and hooks to add whatever other features you want for your table. The features from the TanStack Table core are just a solid foundation to build on, with a large focus on performance and DX. diff --git a/docs/reference/index.md b/docs/reference/index.md new file mode 100644 index 0000000000..e0d78fd762 --- /dev/null +++ b/docs/reference/index.md @@ -0,0 +1,11 @@ +--- +id: "@tanstack/table-core" +title: "@tanstack/table-core" +--- + +# @tanstack/table-core + +## Modules + +- [index](index/index.md) +- [static-functions](static-functions/index.md) diff --git a/docs/reference/index/functions/assignPrototypeAPIs.md b/docs/reference/index/functions/assignPrototypeAPIs.md new file mode 100644 index 0000000000..c2576f8699 --- /dev/null +++ b/docs/reference/index/functions/assignPrototypeAPIs.md @@ -0,0 +1,62 @@ +--- +id: assignPrototypeAPIs +title: assignPrototypeAPIs +--- + +# Function: assignPrototypeAPIs() + +```ts +function assignPrototypeAPIs( + feature, + prototype, + table, + apis): void; +``` + +Defined in: [utils.ts:403](https://github.com/TanStack/table/blob/main/packages/table-core/src/utils.ts#L403) + +Assigns API methods to a prototype object for memory-efficient method sharing. +All instances created with this prototype will share the same method references. + +For memoized methods, the memo state is lazily created and stored on each instance. +This provides the best of both worlds: shared method code + per-instance caching. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TDeps + +`TDeps` *extends* readonly `any`[] + +### TDepArgs + +`TDepArgs` + +## Parameters + +### feature + +keyof `TFeatures` & `string` + +### prototype + +`Record`\<`string`, `any`\> + +### table + +[`Table_Internal`](../type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### apis + +[`PrototypeAPIObject`](../type-aliases/PrototypeAPIObject.md)\<`TDeps`, [`NoInfer`](../type-aliases/NoInfer.md)\<`TDepArgs`\>\> + +## Returns + +`void` diff --git a/docs/reference/index/functions/assignTableAPIs.md b/docs/reference/index/functions/assignTableAPIs.md new file mode 100644 index 0000000000..ad5eb72d3e --- /dev/null +++ b/docs/reference/index/functions/assignTableAPIs.md @@ -0,0 +1,54 @@ +--- +id: assignTableAPIs +title: assignTableAPIs +--- + +# Function: assignTableAPIs() + +```ts +function assignTableAPIs( + feature, + table, + apis): void; +``` + +Defined in: [utils.ts:361](https://github.com/TanStack/table/blob/main/packages/table-core/src/utils.ts#L361) + +Assigns Table API methods directly to the table instance. +Unlike row/cell/column/header, the table is a singleton so methods are assigned directly. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TDeps + +`TDeps` *extends* readonly `any`[] + +### TDepArgs + +`TDepArgs` + +## Parameters + +### feature + +keyof `TFeatures` & `string` + +### table + +[`Table_Internal`](../type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### apis + +[`APIObject`](../type-aliases/APIObject.md)\<`TDeps`, [`NoInfer`](../type-aliases/NoInfer.md)\<`TDepArgs`\>\> + +## Returns + +`void` diff --git a/docs/reference/index/functions/buildHeaderGroups.md b/docs/reference/index/functions/buildHeaderGroups.md new file mode 100644 index 0000000000..95b924cf37 --- /dev/null +++ b/docs/reference/index/functions/buildHeaderGroups.md @@ -0,0 +1,56 @@ +--- +id: buildHeaderGroups +title: buildHeaderGroups +--- + +# Function: buildHeaderGroups() + +```ts +function buildHeaderGroups( + allColumns, + columnsToGroup, + table, + headerFamily?): HeaderGroup[]; +``` + +Defined in: [core/headers/buildHeaderGroups.ts:16](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/buildHeaderGroups.ts#L16) + +Builds the nested header group structure for a table. + +The result accounts for visible leaf columns, pinned column groups, and placeholder headers needed to render multi-level headers. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### allColumns + +[`Column`](../type-aliases/Column.md)\<`TFeatures`, `TData`, `TValue`\>[] + +### columnsToGroup + +[`Column`](../type-aliases/Column.md)\<`TFeatures`, `TData`, `TValue`\>[] + +### table + +[`Table_Internal`](../type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### headerFamily? + +`"left"` | `"right"` | `"center"` + +## Returns + +[`HeaderGroup`](../type-aliases/HeaderGroup.md)\<`TFeatures`, `TData`\>[] diff --git a/docs/reference/index/functions/callMemoOrStaticFn.md b/docs/reference/index/functions/callMemoOrStaticFn.md new file mode 100644 index 0000000000..a0947c8ca5 --- /dev/null +++ b/docs/reference/index/functions/callMemoOrStaticFn.md @@ -0,0 +1,50 @@ +--- +id: callMemoOrStaticFn +title: callMemoOrStaticFn +--- + +# Function: callMemoOrStaticFn() + +```ts +function callMemoOrStaticFn( + obj, + fnKey, + staticFn, ... +args): ReturnType; +``` + +Defined in: [utils.ts:451](https://github.com/TanStack/table/blob/main/packages/table-core/src/utils.ts#L451) + +Looks to run the memoized function with the builder pattern on the object if it exists, otherwise fallback to the static method passed in. + +## Type Parameters + +### TObject + +`TObject` *extends* `Record`\<`string`, `any`\> + +### TStaticFn + +`TStaticFn` *extends* `AnyFunction` + +## Parameters + +### obj + +`TObject` + +### fnKey + +`string` + +### staticFn + +`TStaticFn` + +### args + +...`Parameters`\<`TStaticFn`\> *extends* \[`any`, `...Rest[]`\] ? `Rest` : `never` + +## Returns + +`ReturnType`\<`TStaticFn`\> diff --git a/docs/reference/index/functions/cloneState.md b/docs/reference/index/functions/cloneState.md new file mode 100644 index 0000000000..d7d74767e1 --- /dev/null +++ b/docs/reference/index/functions/cloneState.md @@ -0,0 +1,32 @@ +--- +id: cloneState +title: cloneState +--- + +# Function: cloneState() + +```ts +function cloneState(value): T; +``` + +Defined in: [utils.ts:22](https://github.com/TanStack/table/blob/main/packages/table-core/src/utils.ts#L22) + +Clones table state values while preserving non-plain objects. + +Plain objects and arrays are copied recursively so state updates can avoid mutating existing references. + +## Type Parameters + +### T + +`T` + +## Parameters + +### value + +`T` + +## Returns + +`T` diff --git a/docs/reference/index/functions/constructCell.md b/docs/reference/index/functions/constructCell.md new file mode 100644 index 0000000000..4992d00fc8 --- /dev/null +++ b/docs/reference/index/functions/constructCell.md @@ -0,0 +1,51 @@ +--- +id: constructCell +title: constructCell +--- + +# Function: constructCell() + +```ts +function constructCell( + column, + row, +table): Cell; +``` + +Defined in: [core/cells/constructCell.ts:31](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/constructCell.ts#L31) + +Constructs a cell instance from normalized table internals. + +This wires core properties, feature prototype APIs, and instance data used by table rendering and row-model operations. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column`](../type-aliases/Column.md)\<`TFeatures`, `TData`, `TValue`\> + +### row + +[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\> + +### table + +[`Table_Internal`](../type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`Cell`](../type-aliases/Cell.md)\<`TFeatures`, `TData`, `TValue`\> diff --git a/docs/reference/index/functions/constructColumn.md b/docs/reference/index/functions/constructColumn.md new file mode 100644 index 0000000000..d80028439c --- /dev/null +++ b/docs/reference/index/functions/constructColumn.md @@ -0,0 +1,56 @@ +--- +id: constructColumn +title: constructColumn +--- + +# Function: constructColumn() + +```ts +function constructColumn( + table, + columnDef, + depth, +parent?): Column; +``` + +Defined in: [core/columns/constructColumn.ts:35](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/constructColumn.ts#L35) + +Constructs a column instance from normalized table internals. + +This wires core properties, feature prototype APIs, and instance data used by table rendering and row-model operations. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### table + +[`Table_Internal`](../type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### columnDef + +[`ColumnDef`](../type-aliases/ColumnDef.md)\<`TFeatures`, `TData`, `TValue`\> + +### depth + +`number` + +### parent? + +[`Column`](../type-aliases/Column.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +[`Column`](../type-aliases/Column.md)\<`TFeatures`, `TData`, `TValue`\> diff --git a/docs/reference/index/functions/constructColumnFacetingFeature.md b/docs/reference/index/functions/constructColumnFacetingFeature.md new file mode 100644 index 0000000000..122d50defa --- /dev/null +++ b/docs/reference/index/functions/constructColumnFacetingFeature.md @@ -0,0 +1,30 @@ +--- +id: constructColumnFacetingFeature +title: constructColumnFacetingFeature +--- + +# Function: constructColumnFacetingFeature() + +```ts +function constructColumnFacetingFeature(): TableFeature>; +``` + +Defined in: [features/column-faceting/columnFacetingFeature.ts:37](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.ts#L37) + +Creates the stock column faceting feature. + +The returned feature registers its state defaults, option defaults, and instance APIs so it can be included in a `tableFeatures({ ... })` call. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Returns + +[`TableFeature`](../interfaces/TableFeature.md)\<[`ColumnFacetingFeatureConstructors`](../interfaces/ColumnFacetingFeatureConstructors.md)\<`TFeatures`, `TData`\>\> diff --git a/docs/reference/index/functions/constructColumnFilteringFeature.md b/docs/reference/index/functions/constructColumnFilteringFeature.md new file mode 100644 index 0000000000..2685c27ed1 --- /dev/null +++ b/docs/reference/index/functions/constructColumnFilteringFeature.md @@ -0,0 +1,30 @@ +--- +id: constructColumnFilteringFeature +title: constructColumnFilteringFeature +--- + +# Function: constructColumnFilteringFeature() + +```ts +function constructColumnFilteringFeature(): TableFeature>; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.ts:52](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.ts#L52) + +Creates the stock column filtering feature. + +The returned feature registers its state defaults, option defaults, and instance APIs so it can be included in a `tableFeatures({ ... })` call. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Returns + +[`TableFeature`](../interfaces/TableFeature.md)\<[`ColumnFilteringFeatureConstructors`](../interfaces/ColumnFilteringFeatureConstructors.md)\<`TFeatures`, `TData`\>\> diff --git a/docs/reference/index/functions/constructColumnGroupingFeature.md b/docs/reference/index/functions/constructColumnGroupingFeature.md new file mode 100644 index 0000000000..96fcd8a3d8 --- /dev/null +++ b/docs/reference/index/functions/constructColumnGroupingFeature.md @@ -0,0 +1,30 @@ +--- +id: constructColumnGroupingFeature +title: constructColumnGroupingFeature +--- + +# Function: constructColumnGroupingFeature() + +```ts +function constructColumnGroupingFeature(): TableFeature>; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.ts:59](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.ts#L59) + +Creates the stock column grouping feature. + +The returned feature registers its state defaults, option defaults, and instance APIs so it can be included in a `tableFeatures({ ... })` call. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Returns + +[`TableFeature`](../interfaces/TableFeature.md)\<[`ColumnGroupingFeatureConstructors`](../interfaces/ColumnGroupingFeatureConstructors.md)\<`TFeatures`, `TData`\>\> diff --git a/docs/reference/index/functions/constructColumnOrderingFeature.md b/docs/reference/index/functions/constructColumnOrderingFeature.md new file mode 100644 index 0000000000..9a06c10bd9 --- /dev/null +++ b/docs/reference/index/functions/constructColumnOrderingFeature.md @@ -0,0 +1,30 @@ +--- +id: constructColumnOrderingFeature +title: constructColumnOrderingFeature +--- + +# Function: constructColumnOrderingFeature() + +```ts +function constructColumnOrderingFeature(): TableFeature>; +``` + +Defined in: [features/column-ordering/columnOrderingFeature.ts:39](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-ordering/columnOrderingFeature.ts#L39) + +Creates the stock column ordering feature. + +The returned feature registers its state defaults, option defaults, and instance APIs so it can be included in a `tableFeatures({ ... })` call. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Returns + +[`TableFeature`](../interfaces/TableFeature.md)\<[`ColumnOrderingFeatureConstructors`](../interfaces/ColumnOrderingFeatureConstructors.md)\<`TFeatures`, `TData`\>\> diff --git a/docs/reference/index/functions/constructColumnPinningFeature.md b/docs/reference/index/functions/constructColumnPinningFeature.md new file mode 100644 index 0000000000..4972f931ff --- /dev/null +++ b/docs/reference/index/functions/constructColumnPinningFeature.md @@ -0,0 +1,30 @@ +--- +id: constructColumnPinningFeature +title: constructColumnPinningFeature +--- + +# Function: constructColumnPinningFeature() + +```ts +function constructColumnPinningFeature(): TableFeature>; +``` + +Defined in: [features/column-pinning/columnPinningFeature.ts:69](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.ts#L69) + +Creates the stock column pinning feature. + +The returned feature registers its state defaults, option defaults, and instance APIs so it can be included in a `tableFeatures({ ... })` call. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Returns + +[`TableFeature`](../interfaces/TableFeature.md)\<[`ColumnPinningFeatureConstructors`](../interfaces/ColumnPinningFeatureConstructors.md)\<`TFeatures`, `TData`\>\> diff --git a/docs/reference/index/functions/constructColumnResizingFeature.md b/docs/reference/index/functions/constructColumnResizingFeature.md new file mode 100644 index 0000000000..9debde2a3f --- /dev/null +++ b/docs/reference/index/functions/constructColumnResizingFeature.md @@ -0,0 +1,30 @@ +--- +id: constructColumnResizingFeature +title: constructColumnResizingFeature +--- + +# Function: constructColumnResizingFeature() + +```ts +function constructColumnResizingFeature(): TableFeature>; +``` + +Defined in: [features/column-resizing/columnResizingFeature.ts:40](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.ts#L40) + +Creates the stock column resizing feature. + +The returned feature registers its state defaults, option defaults, and instance APIs so it can be included in a `tableFeatures({ ... })` call. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Returns + +[`TableFeature`](../interfaces/TableFeature.md)\<[`ColumnResizingFeatureConstructors`](../interfaces/ColumnResizingFeatureConstructors.md)\<`TFeatures`, `TData`\>\> diff --git a/docs/reference/index/functions/constructColumnSizingFeature.md b/docs/reference/index/functions/constructColumnSizingFeature.md new file mode 100644 index 0000000000..2d847e5747 --- /dev/null +++ b/docs/reference/index/functions/constructColumnSizingFeature.md @@ -0,0 +1,30 @@ +--- +id: constructColumnSizingFeature +title: constructColumnSizingFeature +--- + +# Function: constructColumnSizingFeature() + +```ts +function constructColumnSizingFeature(): TableFeature>; +``` + +Defined in: [features/column-sizing/columnSizingFeature.ts:52](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.ts#L52) + +Creates the stock column sizing feature. + +The returned feature registers its state defaults, option defaults, and instance APIs so it can be included in a `tableFeatures({ ... })` call. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Returns + +[`TableFeature`](../interfaces/TableFeature.md)\<[`ColumnSizingFeatureConstructors`](../interfaces/ColumnSizingFeatureConstructors.md)\<`TFeatures`, `TData`\>\> diff --git a/docs/reference/index/functions/constructColumnVisibilityFeature.md b/docs/reference/index/functions/constructColumnVisibilityFeature.md new file mode 100644 index 0000000000..8279a7b591 --- /dev/null +++ b/docs/reference/index/functions/constructColumnVisibilityFeature.md @@ -0,0 +1,30 @@ +--- +id: constructColumnVisibilityFeature +title: constructColumnVisibilityFeature +--- + +# Function: constructColumnVisibilityFeature() + +```ts +function constructColumnVisibilityFeature(): TableFeature>; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.ts:56](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.ts#L56) + +Creates the stock column visibility feature. + +The returned feature registers its state defaults, option defaults, and instance APIs so it can be included in a `tableFeatures({ ... })` call. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Returns + +[`TableFeature`](../interfaces/TableFeature.md)\<[`ColumnVisibilityFeatureConstructors`](../interfaces/ColumnVisibilityFeatureConstructors.md)\<`TFeatures`, `TData`\>\> diff --git a/docs/reference/index/functions/constructCoreCellsFeature.md b/docs/reference/index/functions/constructCoreCellsFeature.md new file mode 100644 index 0000000000..d815a4ac2a --- /dev/null +++ b/docs/reference/index/functions/constructCoreCellsFeature.md @@ -0,0 +1,30 @@ +--- +id: constructCoreCellsFeature +title: constructCoreCellsFeature +--- + +# Function: constructCoreCellsFeature() + +```ts +function constructCoreCellsFeature(): TableFeature>; +``` + +Defined in: [core/cells/coreCellsFeature.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.ts#L24) + +Creates the stock core cells feature. + +The returned feature registers its state defaults, option defaults, and instance APIs so it can be included in a `tableFeatures({ ... })` call. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Returns + +[`TableFeature`](../interfaces/TableFeature.md)\<[`CoreCellsFeatureConstructors`](../interfaces/CoreCellsFeatureConstructors.md)\<`TFeatures`, `TData`\>\> diff --git a/docs/reference/index/functions/constructCoreColumnsFeature.md b/docs/reference/index/functions/constructCoreColumnsFeature.md new file mode 100644 index 0000000000..d8aa5a903d --- /dev/null +++ b/docs/reference/index/functions/constructCoreColumnsFeature.md @@ -0,0 +1,30 @@ +--- +id: constructCoreColumnsFeature +title: constructCoreColumnsFeature +--- + +# Function: constructCoreColumnsFeature() + +```ts +function constructCoreColumnsFeature(): TableFeature>; +``` + +Defined in: [core/columns/coreColumnsFeature.ts:34](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.ts#L34) + +Creates the stock core columns feature. + +The returned feature registers its state defaults, option defaults, and instance APIs so it can be included in a `tableFeatures({ ... })` call. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Returns + +[`TableFeature`](../interfaces/TableFeature.md)\<[`CoreColumnsFeatureConstructors`](../interfaces/CoreColumnsFeatureConstructors.md)\<`TFeatures`, `TData`\>\> diff --git a/docs/reference/index/functions/constructCoreHeadersFeature.md b/docs/reference/index/functions/constructCoreHeadersFeature.md new file mode 100644 index 0000000000..03588dd9e8 --- /dev/null +++ b/docs/reference/index/functions/constructCoreHeadersFeature.md @@ -0,0 +1,30 @@ +--- +id: constructCoreHeadersFeature +title: constructCoreHeadersFeature +--- + +# Function: constructCoreHeadersFeature() + +```ts +function constructCoreHeadersFeature(): TableFeature>; +``` + +Defined in: [core/headers/coreHeadersFeature.ts:36](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.ts#L36) + +Creates the stock core headers feature. + +The returned feature registers its state defaults, option defaults, and instance APIs so it can be included in a `tableFeatures({ ... })` call. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Returns + +[`TableFeature`](../interfaces/TableFeature.md)\<[`CoreHeadersFeatureConstructors`](../interfaces/CoreHeadersFeatureConstructors.md)\<`TFeatures`, `TData`\>\> diff --git a/docs/reference/index/functions/constructCoreRowModelsFeature.md b/docs/reference/index/functions/constructCoreRowModelsFeature.md new file mode 100644 index 0000000000..cdea1e98bb --- /dev/null +++ b/docs/reference/index/functions/constructCoreRowModelsFeature.md @@ -0,0 +1,30 @@ +--- +id: constructCoreRowModelsFeature +title: constructCoreRowModelsFeature +--- + +# Function: constructCoreRowModelsFeature() + +```ts +function constructCoreRowModelsFeature(): TableFeature>; +``` + +Defined in: [core/row-models/coreRowModelsFeature.ts:32](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.ts#L32) + +Creates the stock core row models feature. + +The returned feature registers its state defaults, option defaults, and instance APIs so it can be included in a `tableFeatures({ ... })` call. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Returns + +[`TableFeature`](../interfaces/TableFeature.md)\<[`CoreRowModelsFeatureConstructors`](../interfaces/CoreRowModelsFeatureConstructors.md)\<`TFeatures`, `TData`\>\> diff --git a/docs/reference/index/functions/constructCoreRowsFeature.md b/docs/reference/index/functions/constructCoreRowsFeature.md new file mode 100644 index 0000000000..cc3244ce65 --- /dev/null +++ b/docs/reference/index/functions/constructCoreRowsFeature.md @@ -0,0 +1,30 @@ +--- +id: constructCoreRowsFeature +title: constructCoreRowsFeature +--- + +# Function: constructCoreRowsFeature() + +```ts +function constructCoreRowsFeature(): TableFeature>; +``` + +Defined in: [core/rows/coreRowsFeature.ts:36](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.ts#L36) + +Creates the stock core rows feature. + +The returned feature registers its state defaults, option defaults, and instance APIs so it can be included in a `tableFeatures({ ... })` call. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Returns + +[`TableFeature`](../interfaces/TableFeature.md)\<[`CoreRowsFeatureConstructors`](../interfaces/CoreRowsFeatureConstructors.md)\<`TFeatures`, `TData`\>\> diff --git a/docs/reference/index/functions/constructCoreTablesFeature.md b/docs/reference/index/functions/constructCoreTablesFeature.md new file mode 100644 index 0000000000..59fd9a512d --- /dev/null +++ b/docs/reference/index/functions/constructCoreTablesFeature.md @@ -0,0 +1,30 @@ +--- +id: constructCoreTablesFeature +title: constructCoreTablesFeature +--- + +# Function: constructCoreTablesFeature() + +```ts +function constructCoreTablesFeature(): TableFeature>; +``` + +Defined in: [core/table/coreTablesFeature.ts:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.ts#L20) + +Creates the stock core tables feature. + +The returned feature registers its state defaults, option defaults, and instance APIs so it can be included in a `tableFeatures({ ... })` call. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Returns + +[`TableFeature`](../interfaces/TableFeature.md)\<[`CoreTablesFeatureConstructors`](../interfaces/CoreTablesFeatureConstructors.md)\<`TFeatures`, `TData`\>\> diff --git a/docs/reference/index/functions/constructGlobalFilteringFeature.md b/docs/reference/index/functions/constructGlobalFilteringFeature.md new file mode 100644 index 0000000000..07e28b6eb2 --- /dev/null +++ b/docs/reference/index/functions/constructGlobalFilteringFeature.md @@ -0,0 +1,30 @@ +--- +id: constructGlobalFilteringFeature +title: constructGlobalFilteringFeature +--- + +# Function: constructGlobalFilteringFeature() + +```ts +function constructGlobalFilteringFeature(): TableFeature>; +``` + +Defined in: [features/global-filtering/globalFilteringFeature.ts:39](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.ts#L39) + +Creates the stock global filtering feature. + +The returned feature registers its state defaults, option defaults, and instance APIs so it can be included in a `tableFeatures({ ... })` call. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Returns + +[`TableFeature`](../interfaces/TableFeature.md)\<[`GlobalFilteringFeatureConstructors`](../interfaces/GlobalFilteringFeatureConstructors.md)\<`TFeatures`, `TData`\>\> diff --git a/docs/reference/index/functions/constructHeader.md b/docs/reference/index/functions/constructHeader.md new file mode 100644 index 0000000000..4aac187c9d --- /dev/null +++ b/docs/reference/index/functions/constructHeader.md @@ -0,0 +1,69 @@ +--- +id: constructHeader +title: constructHeader +--- + +# Function: constructHeader() + +```ts +function constructHeader( + table, + column, +options): Header; +``` + +Defined in: [core/headers/constructHeader.ts:30](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/constructHeader.ts#L30) + +Constructs a header instance from normalized table internals. + +This wires core properties, feature prototype APIs, and instance data used by table rendering and row-model operations. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### table + +[`Table_Internal`](../type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### column + +[`Column`](../type-aliases/Column.md)\<`TFeatures`, `TData`, `TValue`\> + +### options + +#### depth + +`number` + +#### id? + +`string` + +#### index + +`number` + +#### isPlaceholder? + +`boolean` + +#### placeholderId? + +`string` + +## Returns + +[`Header`](../type-aliases/Header.md)\<`TFeatures`, `TData`, `TValue`\> diff --git a/docs/reference/index/functions/constructRow.md b/docs/reference/index/functions/constructRow.md new file mode 100644 index 0000000000..7e3c131e03 --- /dev/null +++ b/docs/reference/index/functions/constructRow.md @@ -0,0 +1,67 @@ +--- +id: constructRow +title: constructRow +--- + +# Function: constructRow() + +```ts +function constructRow( + table, + id, + original, + rowIndex, + depth, + subRows?, +parentId?): Row; +``` + +Defined in: [core/rows/constructRow.ts:29](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/constructRow.ts#L29) + +Constructs a row instance from normalized table internals. + +This wires core properties, feature prototype APIs, and instance data used by table rendering and row-model operations. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### id + +`string` + +### original + +`TData` + +### rowIndex + +`number` + +### depth + +`number` + +### subRows? + +[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\>[] + +### parentId? + +`string` + +## Returns + +[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/functions/constructRowExpandingFeature.md b/docs/reference/index/functions/constructRowExpandingFeature.md new file mode 100644 index 0000000000..f12f8e8a36 --- /dev/null +++ b/docs/reference/index/functions/constructRowExpandingFeature.md @@ -0,0 +1,30 @@ +--- +id: constructRowExpandingFeature +title: constructRowExpandingFeature +--- + +# Function: constructRowExpandingFeature() + +```ts +function constructRowExpandingFeature(): TableFeature>; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.ts:51](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.ts#L51) + +Creates the stock row expanding feature. + +The returned feature registers its state defaults, option defaults, and instance APIs so it can be included in a `tableFeatures({ ... })` call. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Returns + +[`TableFeature`](../interfaces/TableFeature.md)\<[`RowExpandingFeatureConstructors`](../interfaces/RowExpandingFeatureConstructors.md)\<`TFeatures`, `TData`\>\> diff --git a/docs/reference/index/functions/constructRowPaginationFeature.md b/docs/reference/index/functions/constructRowPaginationFeature.md new file mode 100644 index 0000000000..d7af1271c4 --- /dev/null +++ b/docs/reference/index/functions/constructRowPaginationFeature.md @@ -0,0 +1,30 @@ +--- +id: constructRowPaginationFeature +title: constructRowPaginationFeature +--- + +# Function: constructRowPaginationFeature() + +```ts +function constructRowPaginationFeature(): TableFeature>; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.ts:47](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.ts#L47) + +Creates the stock row pagination feature. + +The returned feature registers its state defaults, option defaults, and instance APIs so it can be included in a `tableFeatures({ ... })` call. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Returns + +[`TableFeature`](../interfaces/TableFeature.md)\<[`RowPaginationFeatureConstructors`](../interfaces/RowPaginationFeatureConstructors.md)\<`TFeatures`, `TData`\>\> diff --git a/docs/reference/index/functions/constructRowPinningFeature.md b/docs/reference/index/functions/constructRowPinningFeature.md new file mode 100644 index 0000000000..42450c459d --- /dev/null +++ b/docs/reference/index/functions/constructRowPinningFeature.md @@ -0,0 +1,30 @@ +--- +id: constructRowPinningFeature +title: constructRowPinningFeature +--- + +# Function: constructRowPinningFeature() + +```ts +function constructRowPinningFeature(): TableFeature>; +``` + +Defined in: [features/row-pinning/rowPinningFeature.ts:43](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.ts#L43) + +Creates the stock row pinning feature. + +The returned feature registers its state defaults, option defaults, and instance APIs so it can be included in a `tableFeatures({ ... })` call. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Returns + +[`TableFeature`](../interfaces/TableFeature.md)\<[`RowPinningFeatureConstructors`](../interfaces/RowPinningFeatureConstructors.md)\<`TFeatures`, `TData`\>\> diff --git a/docs/reference/index/functions/constructRowSelectionFeature.md b/docs/reference/index/functions/constructRowSelectionFeature.md new file mode 100644 index 0000000000..e443b17b11 --- /dev/null +++ b/docs/reference/index/functions/constructRowSelectionFeature.md @@ -0,0 +1,30 @@ +--- +id: constructRowSelectionFeature +title: constructRowSelectionFeature +--- + +# Function: constructRowSelectionFeature() + +```ts +function constructRowSelectionFeature(): TableFeature>; +``` + +Defined in: [features/row-selection/rowSelectionFeature.ts:55](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.ts#L55) + +Creates the stock row selection feature. + +The returned feature registers its state defaults, option defaults, and instance APIs so it can be included in a `tableFeatures({ ... })` call. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Returns + +[`TableFeature`](../interfaces/TableFeature.md)\<[`RowSelectionFeatureConstructors`](../interfaces/RowSelectionFeatureConstructors.md)\<`TFeatures`, `TData`\>\> diff --git a/docs/reference/index/functions/constructRowSortingFeature.md b/docs/reference/index/functions/constructRowSortingFeature.md new file mode 100644 index 0000000000..9b1aae9ad2 --- /dev/null +++ b/docs/reference/index/functions/constructRowSortingFeature.md @@ -0,0 +1,30 @@ +--- +id: constructRowSortingFeature +title: constructRowSortingFeature +--- + +# Function: constructRowSortingFeature() + +```ts +function constructRowSortingFeature(): TableFeature>; +``` + +Defined in: [features/row-sorting/rowSortingFeature.ts:55](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.ts#L55) + +Creates the stock row sorting feature. + +The returned feature registers its state defaults, option defaults, and instance APIs so it can be included in a `tableFeatures({ ... })` call. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Returns + +[`TableFeature`](../interfaces/TableFeature.md)\<[`RowSortingFeatureConstructors`](../interfaces/RowSortingFeatureConstructors.md)\<`TFeatures`, `TData`\>\> diff --git a/docs/reference/index/functions/constructTable.md b/docs/reference/index/functions/constructTable.md new file mode 100644 index 0000000000..544a93cc0c --- /dev/null +++ b/docs/reference/index/functions/constructTable.md @@ -0,0 +1,36 @@ +--- +id: constructTable +title: constructTable +--- + +# Function: constructTable() + +```ts +function constructTable(tableOptions): Table; +``` + +Defined in: [core/table/constructTable.ts:31](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/constructTable.ts#L31) + +Constructs a table instance from normalized table internals. + +This wires core properties, feature prototype APIs, and instance data used by table rendering and row-model operations. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Parameters + +### tableOptions + +[`TableOptions`](../type-aliases/TableOptions.md)\<`TFeatures`, `TData`\> + +## Returns + +[`Table`](../type-aliases/Table.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/functions/createColumnHelper.md b/docs/reference/index/functions/createColumnHelper.md new file mode 100644 index 0000000000..878ce28895 --- /dev/null +++ b/docs/reference/index/functions/createColumnHelper.md @@ -0,0 +1,41 @@ +--- +id: createColumnHelper +title: createColumnHelper +--- + +# Function: createColumnHelper() + +```ts +function createColumnHelper(): ColumnHelper; +``` + +Defined in: [helpers/columnHelper.ts:94](https://github.com/TanStack/table/blob/main/packages/table-core/src/helpers/columnHelper.ts#L94) + +A helper utility for creating column definitions with slightly better type inference for each individual column. +The `TValue` generic is inferred based on the accessor key or function provided. +**Note:** From a JavaScript perspective, the functions in these helpers do not do anything. They are only used to help TypeScript infer the correct types for the column definitions. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Returns + +[`ColumnHelper`](../type-aliases/ColumnHelper.md)\<`TFeatures`, `TData`\> + +## Example + +```tsx +const helper = createColumnHelper() // _features is the result of `tableFeatures({})` helper +const columns = [ + helper.display({ id: 'actions', header: 'Actions' }), + helper.accessor('firstName', {}), + helper.accessor((row) => row.lastName, {} +] +``` diff --git a/docs/reference/index/functions/createCoreRowModel.md b/docs/reference/index/functions/createCoreRowModel.md new file mode 100644 index 0000000000..ed9e83a5ea --- /dev/null +++ b/docs/reference/index/functions/createCoreRowModel.md @@ -0,0 +1,48 @@ +--- +id: createCoreRowModel +title: createCoreRowModel +--- + +# Function: createCoreRowModel() + +```ts +function createCoreRowModel(): (table) => () => RowModel; +``` + +Defined in: [core/row-models/createCoreRowModel.ts:15](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/createCoreRowModel.ts#L15) + +Creates a memoized core row model factory. + +The factory reads the relevant table state atoms and options, then returns a row model function used by the table row-model pipeline. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Returns + +```ts +(table): () => RowModel; +``` + +### Parameters + +#### table + +[`Table`](../type-aliases/Table.md)\<`TFeatures`, `TData`\> + +### Returns + +```ts +(): RowModel; +``` + +#### Returns + +[`RowModel`](../interfaces/RowModel.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/functions/createExpandedRowModel.md b/docs/reference/index/functions/createExpandedRowModel.md new file mode 100644 index 0000000000..28498967bc --- /dev/null +++ b/docs/reference/index/functions/createExpandedRowModel.md @@ -0,0 +1,48 @@ +--- +id: createExpandedRowModel +title: createExpandedRowModel +--- + +# Function: createExpandedRowModel() + +```ts +function createExpandedRowModel(): (table) => () => RowModel; +``` + +Defined in: [features/row-expanding/createExpandedRowModel.ts:14](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/createExpandedRowModel.ts#L14) + +Creates a memoized expanded row model factory. + +The factory reads the relevant table state atoms and options, then returns a row model function used by the table row-model pipeline. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) = `any` + +## Returns + +```ts +(table): () => RowModel; +``` + +### Parameters + +#### table + +[`Table`](../type-aliases/Table.md)\<`TFeatures`, `TData`\> + +### Returns + +```ts +(): RowModel; +``` + +#### Returns + +[`RowModel`](../interfaces/RowModel.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/functions/createFacetedMinMaxValues.md b/docs/reference/index/functions/createFacetedMinMaxValues.md new file mode 100644 index 0000000000..8460fb348f --- /dev/null +++ b/docs/reference/index/functions/createFacetedMinMaxValues.md @@ -0,0 +1,52 @@ +--- +id: createFacetedMinMaxValues +title: createFacetedMinMaxValues +--- + +# Function: createFacetedMinMaxValues() + +```ts +function createFacetedMinMaxValues(): (table, columnId) => () => [number, number] | undefined; +``` + +Defined in: [features/column-faceting/createFacetedMinMaxValues.ts:13](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/createFacetedMinMaxValues.ts#L13) + +Creates a memoized faceted min max 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. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) = `any` + +## Returns + +```ts +(table, columnId): () => [number, number] | undefined; +``` + +### Parameters + +#### table + +[`Table`](../type-aliases/Table.md)\<`TFeatures`, `TData`\> + +#### columnId + +`string` + +### Returns + +```ts +(): [number, number] | undefined; +``` + +#### Returns + +\[`number`, `number`\] \| `undefined` diff --git a/docs/reference/index/functions/createFacetedRowModel.md b/docs/reference/index/functions/createFacetedRowModel.md new file mode 100644 index 0000000000..772a72a4b4 --- /dev/null +++ b/docs/reference/index/functions/createFacetedRowModel.md @@ -0,0 +1,52 @@ +--- +id: createFacetedRowModel +title: createFacetedRowModel +--- + +# Function: createFacetedRowModel() + +```ts +function createFacetedRowModel(): (table, columnId) => () => RowModel; +``` + +Defined in: [features/column-faceting/createFacetedRowModel.ts:18](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/createFacetedRowModel.ts#L18) + +Creates a memoized faceted row model factory. + +The factory reads the relevant table state atoms and options, then returns a row model function used by the table row-model pipeline. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) = `any` + +## Returns + +```ts +(table, columnId): () => RowModel; +``` + +### Parameters + +#### table + +[`Table`](../type-aliases/Table.md)\<`TFeatures`, `TData`\> + +#### columnId + +`string` + +### Returns + +```ts +(): RowModel; +``` + +#### Returns + +[`RowModel`](../interfaces/RowModel.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/functions/createFacetedUniqueValues.md b/docs/reference/index/functions/createFacetedUniqueValues.md new file mode 100644 index 0000000000..b57c386c31 --- /dev/null +++ b/docs/reference/index/functions/createFacetedUniqueValues.md @@ -0,0 +1,52 @@ +--- +id: createFacetedUniqueValues +title: createFacetedUniqueValues +--- + +# Function: createFacetedUniqueValues() + +```ts +function createFacetedUniqueValues(): (table, columnId) => () => Map; +``` + +Defined in: [features/column-faceting/createFacetedUniqueValues.ts:13](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/createFacetedUniqueValues.ts#L13) + +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. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) = `any` + +## Returns + +```ts +(table, columnId): () => Map; +``` + +### Parameters + +#### table + +[`Table`](../type-aliases/Table.md)\<`TFeatures`, `TData`\> + +#### columnId + +`string` + +### Returns + +```ts +(): Map; +``` + +#### Returns + +`Map`\<`any`, `number`\> diff --git a/docs/reference/index/functions/createFilteredRowModel.md b/docs/reference/index/functions/createFilteredRowModel.md new file mode 100644 index 0000000000..5182eb3295 --- /dev/null +++ b/docs/reference/index/functions/createFilteredRowModel.md @@ -0,0 +1,54 @@ +--- +id: createFilteredRowModel +title: createFilteredRowModel +--- + +# Function: createFilteredRowModel() + +```ts +function createFilteredRowModel(filterFns): (table) => () => RowModel; +``` + +Defined in: [features/column-filtering/createFilteredRowModel.ts:27](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/createFilteredRowModel.ts#L27) + +Creates a memoized filtered row model factory. + +The factory reads the relevant table state atoms and options, then returns a row model function used by the table row-model pipeline. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) = `any` + +## Parameters + +### filterFns + +`Record`\\> + +## Returns + +```ts +(table): () => RowModel; +``` + +### Parameters + +#### table + +[`Table`](../type-aliases/Table.md)\<`TFeatures`, `TData`\> + +### Returns + +```ts +(): RowModel; +``` + +#### Returns + +[`RowModel`](../interfaces/RowModel.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/functions/createGroupedRowModel.md b/docs/reference/index/functions/createGroupedRowModel.md new file mode 100644 index 0000000000..2141da649d --- /dev/null +++ b/docs/reference/index/functions/createGroupedRowModel.md @@ -0,0 +1,54 @@ +--- +id: createGroupedRowModel +title: createGroupedRowModel +--- + +# Function: createGroupedRowModel() + +```ts +function createGroupedRowModel(aggregationFns): (table) => () => RowModel; +``` + +Defined in: [features/column-grouping/createGroupedRowModel.ts:27](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/createGroupedRowModel.ts#L27) + +Creates a memoized grouped row model factory. + +The factory reads the relevant table state atoms and options, then returns a row model function used by the table row-model pipeline. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) = `any` + +## Parameters + +### aggregationFns + +`Record`\\> + +## Returns + +```ts +(table): () => RowModel; +``` + +### Parameters + +#### table + +[`Table`](../type-aliases/Table.md)\<`TFeatures`, `TData`\> + +### Returns + +```ts +(): RowModel; +``` + +#### Returns + +[`RowModel`](../interfaces/RowModel.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/functions/createPaginatedRowModel.md b/docs/reference/index/functions/createPaginatedRowModel.md new file mode 100644 index 0000000000..e5874afac6 --- /dev/null +++ b/docs/reference/index/functions/createPaginatedRowModel.md @@ -0,0 +1,48 @@ +--- +id: createPaginatedRowModel +title: createPaginatedRowModel +--- + +# Function: createPaginatedRowModel() + +```ts +function createPaginatedRowModel(): (table) => () => RowModel; +``` + +Defined in: [features/row-pagination/createPaginatedRowModel.ts:15](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/createPaginatedRowModel.ts#L15) + +Creates a memoized paginated row model factory. + +The factory reads the relevant table state atoms and options, then returns a row model function used by the table row-model pipeline. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) = `any` + +## Returns + +```ts +(table): () => RowModel; +``` + +### Parameters + +#### table + +[`Table`](../type-aliases/Table.md)\<`TFeatures`, `TData`\> + +### Returns + +```ts +(): RowModel; +``` + +#### Returns + +[`RowModel`](../interfaces/RowModel.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/functions/createSortedRowModel.md b/docs/reference/index/functions/createSortedRowModel.md new file mode 100644 index 0000000000..d8217acb1d --- /dev/null +++ b/docs/reference/index/functions/createSortedRowModel.md @@ -0,0 +1,54 @@ +--- +id: createSortedRowModel +title: createSortedRowModel +--- + +# Function: createSortedRowModel() + +```ts +function createSortedRowModel(sortFns): (table) => () => RowModel; +``` + +Defined in: [features/row-sorting/createSortedRowModel.ts:17](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/createSortedRowModel.ts#L17) + +Creates a memoized sorted row model factory. + +The factory reads the relevant table state atoms and options, then returns a row model function used by the table row-model pipeline. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Parameters + +### sortFns + +`Record`\\> + +## Returns + +```ts +(table): () => RowModel; +``` + +### Parameters + +#### table + +[`Table`](../type-aliases/Table.md)\<`TFeatures`, `TData`\> + +### Returns + +```ts +(): RowModel; +``` + +#### Returns + +[`RowModel`](../interfaces/RowModel.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/functions/expandRows.md b/docs/reference/index/functions/expandRows.md new file mode 100644 index 0000000000..dd38e8fbdb --- /dev/null +++ b/docs/reference/index/functions/expandRows.md @@ -0,0 +1,36 @@ +--- +id: expandRows +title: expandRows +--- + +# Function: expandRows() + +```ts +function expandRows(rowModel): RowModel; +``` + +Defined in: [features/row-expanding/createExpandedRowModel.ts:61](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/createExpandedRowModel.ts#L61) + +Expands a row model according to the current expanded row state. + +Expanded sub-rows are inserted into the flattened row order while preserving the original row hierarchy. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) = `any` + +## Parameters + +### rowModel + +[`RowModel`](../interfaces/RowModel.md)\<`TFeatures`, `TData`\> + +## Returns + +[`RowModel`](../interfaces/RowModel.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/functions/flattenBy.md b/docs/reference/index/functions/flattenBy.md new file mode 100644 index 0000000000..a67cf48855 --- /dev/null +++ b/docs/reference/index/functions/flattenBy.md @@ -0,0 +1,36 @@ +--- +id: flattenBy +title: flattenBy +--- + +# Function: flattenBy() + +```ts +function flattenBy(arr, getChildren): TNode[]; +``` + +Defined in: [utils.ts:88](https://github.com/TanStack/table/blob/main/packages/table-core/src/utils.ts#L88) + +Flattens a tree of nodes by recursively reading child nodes. + +The original nodes are preserved in depth-first order. + +## Type Parameters + +### TNode + +`TNode` + +## Parameters + +### arr + +`TNode`[] + +### getChildren + +(`item`) => `TNode`[] + +## Returns + +`TNode`[] diff --git a/docs/reference/index/functions/functionalUpdate.md b/docs/reference/index/functions/functionalUpdate.md new file mode 100644 index 0000000000..1904efc4ca --- /dev/null +++ b/docs/reference/index/functions/functionalUpdate.md @@ -0,0 +1,36 @@ +--- +id: functionalUpdate +title: functionalUpdate +--- + +# Function: functionalUpdate() + +```ts +function functionalUpdate(updater, input): T; +``` + +Defined in: [utils.ts:11](https://github.com/TanStack/table/blob/main/packages/table-core/src/utils.ts#L11) + +Applies a TanStack updater to a value. + +If the updater is a function it is called with the previous value; otherwise the updater value is returned directly. + +## Type Parameters + +### T + +`T` + +## Parameters + +### updater + +[`Updater`](../type-aliases/Updater.md)\<`T`\> + +### input + +`T` + +## Returns + +`T` diff --git a/docs/reference/index/functions/getFunctionNameInfo.md b/docs/reference/index/functions/getFunctionNameInfo.md new file mode 100644 index 0000000000..ddda930829 --- /dev/null +++ b/docs/reference/index/functions/getFunctionNameInfo.md @@ -0,0 +1,46 @@ +--- +id: getFunctionNameInfo +title: getFunctionNameInfo +--- + +# Function: getFunctionNameInfo() + +```ts +function getFunctionNameInfo(staticFnName, splitBy): object; +``` + +Defined in: [utils.ts:344](https://github.com/TanStack/table/blob/main/packages/table-core/src/utils.ts#L344) + +Assumes that a function name is in the format of `parentName_fnKey` and returns the `fnKey` and `fnName` in the format of `parentName.fnKey`. + +## Parameters + +### staticFnName + +`string` + +### splitBy + +`"_"` | `"."` + +## Returns + +`object` + +### fnKey + +```ts +fnKey: string; +``` + +### fnName + +```ts +fnName: string; +``` + +### parentName + +```ts +parentName: string; +``` diff --git a/docs/reference/index/functions/getInitialTableState.md b/docs/reference/index/functions/getInitialTableState.md new file mode 100644 index 0000000000..07fbff1cbe --- /dev/null +++ b/docs/reference/index/functions/getInitialTableState.md @@ -0,0 +1,36 @@ +--- +id: getInitialTableState +title: getInitialTableState +--- + +# Function: getInitialTableState() + +```ts +function getInitialTableState(features, initialState): TableState; +``` + +Defined in: [core/table/constructTable.ts:16](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/constructTable.ts#L16) + +Builds the initial table state from registered features and user initial state. + +Each feature contributes its default state before user-provided `initialState` values are merged in. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +## Parameters + +### features + +`TFeatures` + +### initialState + +`Partial`\<[`TableState`](../type-aliases/TableState.md)\<`TFeatures`\>\> | `undefined` + +## Returns + +[`TableState`](../type-aliases/TableState.md)\<`TFeatures`\> diff --git a/docs/reference/index/functions/getMemoFnMeta.md b/docs/reference/index/functions/getMemoFnMeta.md new file mode 100644 index 0000000000..a3ef66ef4c --- /dev/null +++ b/docs/reference/index/functions/getMemoFnMeta.md @@ -0,0 +1,24 @@ +--- +id: getMemoFnMeta +title: getMemoFnMeta +--- + +# Function: getMemoFnMeta() + +```ts +function getMemoFnMeta(fn): MemoFnMeta | null; +``` + +Defined in: [utils.ts:128](https://github.com/TanStack/table/blob/main/packages/table-core/src/utils.ts#L128) + +**`Internal`** + +## Parameters + +### fn + +`any` + +## Returns + +[`MemoFnMeta`](../type-aliases/MemoFnMeta.md) \| `null` diff --git a/docs/reference/index/functions/isFunction.md b/docs/reference/index/functions/isFunction.md new file mode 100644 index 0000000000..9019cd7175 --- /dev/null +++ b/docs/reference/index/functions/isFunction.md @@ -0,0 +1,30 @@ +--- +id: isFunction +title: isFunction +--- + +# Function: isFunction() + +```ts +function isFunction(d): d is T; +``` + +Defined in: [utils.ts:72](https://github.com/TanStack/table/blob/main/packages/table-core/src/utils.ts#L72) + +Returns whether a value is a function. + +## Type Parameters + +### T + +`T` *extends* `AnyFunction` + +## Parameters + +### d + +`any` + +## Returns + +`d is T` diff --git a/docs/reference/index/functions/isNumberArray.md b/docs/reference/index/functions/isNumberArray.md new file mode 100644 index 0000000000..8ca5a104cf --- /dev/null +++ b/docs/reference/index/functions/isNumberArray.md @@ -0,0 +1,24 @@ +--- +id: isNumberArray +title: isNumberArray +--- + +# Function: isNumberArray() + +```ts +function isNumberArray(d): d is number[]; +``` + +Defined in: [utils.ts:79](https://github.com/TanStack/table/blob/main/packages/table-core/src/utils.ts#L79) + +Returns whether a value is an array containing only numbers. + +## Parameters + +### d + +`any` + +## Returns + +`d is number[]` diff --git a/docs/reference/index/functions/makeStateUpdater.md b/docs/reference/index/functions/makeStateUpdater.md new file mode 100644 index 0000000000..c4ad45d9e3 --- /dev/null +++ b/docs/reference/index/functions/makeStateUpdater.md @@ -0,0 +1,52 @@ +--- +id: makeStateUpdater +title: makeStateUpdater +--- + +# Function: makeStateUpdater() + +```ts +function makeStateUpdater(key, instance): (updater) => void; +``` + +Defined in: [utils.ts:56](https://github.com/TanStack/table/blob/main/packages/table-core/src/utils.ts#L56) + +Creates a table state updater for a single state slice. + +The updater writes through the table base atom for the slice and supports both value and functional updater forms. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### K + +`K` *extends* `string` \| `number` \| `symbol` \| `string` & `object` + +## Parameters + +### key + +`K` + +### instance + +[`Table`](../type-aliases/Table.md)\<`TFeatures`, `any`\> + +## Returns + +```ts +(updater): void; +``` + +### Parameters + +#### updater + +`any` + +### Returns + +`void` diff --git a/docs/reference/index/functions/memo.md b/docs/reference/index/functions/memo.md new file mode 100644 index 0000000000..2aa9aea559 --- /dev/null +++ b/docs/reference/index/functions/memo.md @@ -0,0 +1,52 @@ +--- +id: memo +title: memo +--- + +# Function: memo() + +```ts +function memo(__namedParameters): (depArgs?) => TResult; +``` + +Defined in: [utils.ts:146](https://github.com/TanStack/table/blob/main/packages/table-core/src/utils.ts#L146) + +Creates a dependency-tracked memoized function for table internals. + +The memo recomputes only when its dependency tuple changes and can emit debug timing information. + +## Type Parameters + +### TDeps + +`TDeps` *extends* readonly `any`[] + +### TDepArgs + +`TDepArgs` + +### TResult + +`TResult` + +## Parameters + +### \_\_namedParameters + +`MemoOptions`\<`TDeps`, `TDepArgs`, `TResult`\> + +## Returns + +```ts +(depArgs?): TResult; +``` + +### Parameters + +#### depArgs? + +`TDepArgs` + +### Returns + +`TResult` diff --git a/docs/reference/index/functions/noop.md b/docs/reference/index/functions/noop.md new file mode 100644 index 0000000000..e3df388d7d --- /dev/null +++ b/docs/reference/index/functions/noop.md @@ -0,0 +1,18 @@ +--- +id: noop +title: noop +--- + +# Function: noop() + +```ts +function noop(): void; +``` + +Defined in: [utils.ts:49](https://github.com/TanStack/table/blob/main/packages/table-core/src/utils.ts#L49) + +A no-operation function used as a safe default callback. + +## Returns + +`void` diff --git a/docs/reference/index/functions/tableFeatures.md b/docs/reference/index/functions/tableFeatures.md new file mode 100644 index 0000000000..1b0a5c8cd1 --- /dev/null +++ b/docs/reference/index/functions/tableFeatures.md @@ -0,0 +1,40 @@ +--- +id: tableFeatures +title: tableFeatures +--- + +# Function: tableFeatures() + +```ts +function tableFeatures(features): TFeatures; +``` + +Defined in: [helpers/tableFeatures.ts:14](https://github.com/TanStack/table/blob/main/packages/table-core/src/helpers/tableFeatures.ts#L14) + +A helper function to help define the features that are to be imported and applied to a table instance. +Use this utility to make it easier to have the correct type inference for the features that are being imported. +**Note:** It is recommended to use this utility statically outside of a component. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +## Parameters + +### features + +`TFeatures` + +## Returns + +`TFeatures` + +## Example + +``` +import { tableFeatures, columnVisibilityFeature, rowPinningFeature } from '@tanstack/react-table' +const _features = tableFeatures({ columnVisibilityFeature, rowPinningFeature }); +const table = useTable({ _features, rowModels: {}, columns, data }); +``` diff --git a/docs/reference/index/functions/tableMemo.md b/docs/reference/index/functions/tableMemo.md new file mode 100644 index 0000000000..c20b326702 --- /dev/null +++ b/docs/reference/index/functions/tableMemo.md @@ -0,0 +1,56 @@ +--- +id: tableMemo +title: tableMemo +--- + +# Function: tableMemo() + +```ts +function tableMemo(__namedParameters): (depArgs?) => TResult; +``` + +Defined in: [utils.ts:212](https://github.com/TanStack/table/blob/main/packages/table-core/src/utils.ts#L212) + +Creates a table-aware memoized function. + +This wraps `memo` with table debug options and feature metadata so row models and derived APIs can share consistent diagnostics. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TDeps + +`TDeps` *extends* readonly `any`[] + +### TDepArgs + +`TDepArgs` + +### TResult + +`TResult` + +## Parameters + +### \_\_namedParameters + +`TableMemoOptions`\<`TFeatures`, `TDeps`, `TDepArgs`, `TResult`\> + +## Returns + +```ts +(depArgs?): TResult; +``` + +### Parameters + +#### depArgs? + +`TDepArgs` + +### Returns + +`TResult` diff --git a/docs/reference/index/functions/tableOptions.md b/docs/reference/index/functions/tableOptions.md new file mode 100644 index 0000000000..288a9b98a3 --- /dev/null +++ b/docs/reference/index/functions/tableOptions.md @@ -0,0 +1,253 @@ +--- +id: tableOptions +title: tableOptions +--- + +# Function: tableOptions() + +Runtime implementation for `tableOptions`. + +The helper returns the same object it receives; all value comes from the +overloads preserving table option inference at compile time. + +## Call Signature + +```ts +function tableOptions(options): Omit, "_features" | "columns"> & object; +``` + +Defined in: [helpers/tableOptions.ts:10](https://github.com/TanStack/table/blob/main/packages/table-core/src/helpers/tableOptions.ts#L10) + +Returns table options while preserving generic inference. + +This helper is useful when composing reusable table options outside of a framework adapter call. + +### Type Parameters + +#### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +#### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) = `any` + +### Parameters + +#### options + +`Omit`\<[`TableOptions`](../type-aliases/TableOptions.md)\<`TFeatures`, `TData`\>, `"columns"`\> & `object` + +### Returns + +`Omit`\<[`TableOptions`](../type-aliases/TableOptions.md)\<`TFeatures`, `TData`\>, `"_features"` \| `"columns"`\> & `object` + +## Call Signature + +```ts +function tableOptions(options): Omit, "_features" | "data"> & object; +``` + +Defined in: [helpers/tableOptions.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/helpers/tableOptions.ts#L24) + +Returns table options while preserving generic inference when `data` is supplied later. + +### Type Parameters + +#### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +#### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) = `any` + +### Parameters + +#### options + +`Omit`\<[`TableOptions`](../type-aliases/TableOptions.md)\<`TFeatures`, `TData`\>, `"data"`\> & `object` + +### Returns + +`Omit`\<[`TableOptions`](../type-aliases/TableOptions.md)\<`TFeatures`, `TData`\>, `"_features"` \| `"data"`\> & `object` + +## Call Signature + +```ts +function tableOptions(options): Omit, "_features" | "data" | "columns"> & object; +``` + +Defined in: [helpers/tableOptions.ts:38](https://github.com/TanStack/table/blob/main/packages/table-core/src/helpers/tableOptions.ts#L38) + +Returns table options while preserving generic inference when both `data` and `columns` are supplied later. + +### Type Parameters + +#### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +#### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) = `any` + +### Parameters + +#### options + +`Omit`\<[`TableOptions`](../type-aliases/TableOptions.md)\<`TFeatures`, `TData`\>, `"data"` \| `"columns"`\> & `object` + +### Returns + +`Omit`\<[`TableOptions`](../type-aliases/TableOptions.md)\<`TFeatures`, `TData`\>, `"_features"` \| `"data"` \| `"columns"`\> & `object` + +## Call Signature + +```ts +function tableOptions(options): TableOptions; +``` + +Defined in: [helpers/tableOptions.ts:52](https://github.com/TanStack/table/blob/main/packages/table-core/src/helpers/tableOptions.ts#L52) + +Returns a fully specified table options object without changing its runtime value. + +### Type Parameters + +#### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +#### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) = `any` + +### Parameters + +#### options + +[`TableOptions`](../type-aliases/TableOptions.md)\<`TFeatures`, `TData`\> + +### Returns + +[`TableOptions`](../type-aliases/TableOptions.md)\<`TFeatures`, `TData`\> + +## Call Signature + +```ts +function tableOptions(options): Omit, "_features">; +``` + +Defined in: [helpers/tableOptions.ts:60](https://github.com/TanStack/table/blob/main/packages/table-core/src/helpers/tableOptions.ts#L60) + +Returns table options while preserving generic inference when `_features` is supplied by a wrapper. + +### Type Parameters + +#### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +#### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) = `any` + +### Parameters + +#### options + +`Omit`\<[`TableOptions`](../type-aliases/TableOptions.md)\<`TFeatures`, `TData`\>, `"_features"`\> + +### Returns + +`Omit`\<[`TableOptions`](../type-aliases/TableOptions.md)\<`TFeatures`, `TData`\>, `"_features"`\> + +## Call Signature + +```ts +function tableOptions(options): Omit, "data" | "_features">; +``` + +Defined in: [helpers/tableOptions.ts:70](https://github.com/TanStack/table/blob/main/packages/table-core/src/helpers/tableOptions.ts#L70) + +Returns table options while preserving generic inference when `data` and `_features` are supplied by a wrapper. + +### Type Parameters + +#### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +#### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) = `any` + +### Parameters + +#### options + +`Omit`\<[`TableOptions`](../type-aliases/TableOptions.md)\<`TFeatures`, `TData`\>, `"data"` \| `"_features"`\> + +### Returns + +`Omit`\<[`TableOptions`](../type-aliases/TableOptions.md)\<`TFeatures`, `TData`\>, `"data"` \| `"_features"`\> + +## Call Signature + +```ts +function tableOptions(options): Omit, "columns" | "_features">; +``` + +Defined in: [helpers/tableOptions.ts:80](https://github.com/TanStack/table/blob/main/packages/table-core/src/helpers/tableOptions.ts#L80) + +Returns table options while preserving generic inference when `columns` and `_features` are supplied by a wrapper. + +### Type Parameters + +#### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +#### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) = `any` + +### Parameters + +#### options + +`Omit`\<[`TableOptions`](../type-aliases/TableOptions.md)\<`TFeatures`, `TData`\>, `"columns"` \| `"_features"`\> + +### Returns + +`Omit`\<[`TableOptions`](../type-aliases/TableOptions.md)\<`TFeatures`, `TData`\>, `"columns"` \| `"_features"`\> + +## Call Signature + +```ts +function tableOptions(options): Omit, "data" | "columns" | "_features">; +``` + +Defined in: [helpers/tableOptions.ts:90](https://github.com/TanStack/table/blob/main/packages/table-core/src/helpers/tableOptions.ts#L90) + +Returns table options while preserving generic inference when `data`, `columns`, and `_features` are supplied by a wrapper. + +### Type Parameters + +#### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +#### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) = `any` + +### Parameters + +#### options + +`Omit`\<[`TableOptions`](../type-aliases/TableOptions.md)\<`TFeatures`, `TData`\>, `"data"` \| `"columns"` \| `"_features"`\> + +### Returns + +`Omit`\<[`TableOptions`](../type-aliases/TableOptions.md)\<`TFeatures`, `TData`\>, `"data"` \| `"columns"` \| `"_features"`\> diff --git a/docs/reference/index/index.md b/docs/reference/index/index.md new file mode 100644 index 0000000000..a1b4d7fe2c --- /dev/null +++ b/docs/reference/index/index.md @@ -0,0 +1,429 @@ +--- +id: index +title: index +--- + +# index + +## Namespaces + +- [filterFn\_arrIncludes](namespaces/filterFn_arrIncludes/index.md) +- [filterFn\_arrIncludesAll](namespaces/filterFn_arrIncludesAll/index.md) +- [filterFn\_arrIncludesSome](namespaces/filterFn_arrIncludesSome/index.md) +- [filterFn\_equals](namespaces/filterFn_equals/index.md) +- [filterFn\_equalsString](namespaces/filterFn_equalsString/index.md) +- [filterFn\_equalsStringSensitive](namespaces/filterFn_equalsStringSensitive/index.md) +- [filterFn\_greaterThan](namespaces/filterFn_greaterThan/index.md) +- [filterFn\_greaterThanOrEqualTo](namespaces/filterFn_greaterThanOrEqualTo/index.md) +- [filterFn\_includesString](namespaces/filterFn_includesString/index.md) +- [filterFn\_includesStringSensitive](namespaces/filterFn_includesStringSensitive/index.md) +- [filterFn\_inNumberRange](namespaces/filterFn_inNumberRange/index.md) +- [filterFn\_lessThan](namespaces/filterFn_lessThan/index.md) +- [filterFn\_lessThanOrEqualTo](namespaces/filterFn_lessThanOrEqualTo/index.md) +- [filterFn\_weakEquals](namespaces/filterFn_weakEquals/index.md) + +## Interfaces + +- [AggregationFns](interfaces/AggregationFns.md) +- [API](interfaces/API.md) +- [CachedRowModel\_Core](interfaces/CachedRowModel_Core.md) +- [CachedRowModel\_Expanded](interfaces/CachedRowModel_Expanded.md) +- [CachedRowModel\_Faceted](interfaces/CachedRowModel_Faceted.md) +- [CachedRowModel\_Filtered](interfaces/CachedRowModel_Filtered.md) +- [CachedRowModel\_Grouped](interfaces/CachedRowModel_Grouped.md) +- [CachedRowModel\_Paginated](interfaces/CachedRowModel_Paginated.md) +- [CachedRowModel\_Plugins](interfaces/CachedRowModel_Plugins.md) +- [CachedRowModel\_Sorted](interfaces/CachedRowModel_Sorted.md) +- [CachedRowModels\_Plugins](interfaces/CachedRowModels_Plugins.md) +- [Cell\_Cell](interfaces/Cell_Cell.md) +- [Cell\_ColumnGrouping](interfaces/Cell_ColumnGrouping.md) +- [Cell\_Core](interfaces/Cell_Core.md) +- [Cell\_CoreProperties](interfaces/Cell_CoreProperties.md) +- [Cell\_Plugins](interfaces/Cell_Plugins.md) +- [CellContext](interfaces/CellContext.md) +- [Column\_Column](interfaces/Column_Column.md) +- [Column\_ColumnFaceting](interfaces/Column_ColumnFaceting.md) +- [Column\_ColumnFiltering](interfaces/Column_ColumnFiltering.md) +- [Column\_ColumnGrouping](interfaces/Column_ColumnGrouping.md) +- [Column\_ColumnOrdering](interfaces/Column_ColumnOrdering.md) +- [Column\_ColumnPinning](interfaces/Column_ColumnPinning.md) +- [Column\_ColumnResizing](interfaces/Column_ColumnResizing.md) +- [Column\_ColumnSizing](interfaces/Column_ColumnSizing.md) +- [Column\_ColumnVisibility](interfaces/Column_ColumnVisibility.md) +- [Column\_Core](interfaces/Column_Core.md) +- [Column\_CoreProperties](interfaces/Column_CoreProperties.md) +- [Column\_GlobalFiltering](interfaces/Column_GlobalFiltering.md) +- [Column\_Plugins](interfaces/Column_Plugins.md) +- [Column\_RowSorting](interfaces/Column_RowSorting.md) +- [ColumnDef\_ColumnFiltering](interfaces/ColumnDef_ColumnFiltering.md) +- [ColumnDef\_ColumnGrouping](interfaces/ColumnDef_ColumnGrouping.md) +- [ColumnDef\_ColumnPinning](interfaces/ColumnDef_ColumnPinning.md) +- [ColumnDef\_ColumnResizing](interfaces/ColumnDef_ColumnResizing.md) +- [ColumnDef\_ColumnSizing](interfaces/ColumnDef_ColumnSizing.md) +- [ColumnDef\_ColumnVisibility](interfaces/ColumnDef_ColumnVisibility.md) +- [ColumnDef\_GlobalFiltering](interfaces/ColumnDef_GlobalFiltering.md) +- [ColumnDef\_Plugins](interfaces/ColumnDef_Plugins.md) +- [ColumnDef\_RowSorting](interfaces/ColumnDef_RowSorting.md) +- [ColumnDefaultOptions](interfaces/ColumnDefaultOptions.md) +- [ColumnFacetingFeatureConstructors](interfaces/ColumnFacetingFeatureConstructors.md) +- [ColumnFilter](interfaces/ColumnFilter.md) +- [ColumnFilteringFeatureConstructors](interfaces/ColumnFilteringFeatureConstructors.md) +- [ColumnGroupingFeatureConstructors](interfaces/ColumnGroupingFeatureConstructors.md) +- [ColumnMeta](interfaces/ColumnMeta.md) +- [ColumnOrderDefaultOptions](interfaces/ColumnOrderDefaultOptions.md) +- [ColumnOrderingFeatureConstructors](interfaces/ColumnOrderingFeatureConstructors.md) +- [ColumnPinningDefaultOptions](interfaces/ColumnPinningDefaultOptions.md) +- [ColumnPinningFeatureConstructors](interfaces/ColumnPinningFeatureConstructors.md) +- [ColumnPinningState](interfaces/ColumnPinningState.md) +- [ColumnResizingFeatureConstructors](interfaces/ColumnResizingFeatureConstructors.md) +- [columnResizingState](interfaces/columnResizingState.md) +- [ColumnSizingFeatureConstructors](interfaces/ColumnSizingFeatureConstructors.md) +- [ColumnSort](interfaces/ColumnSort.md) +- [ColumnVisibilityFeatureConstructors](interfaces/ColumnVisibilityFeatureConstructors.md) +- [CoreCellsFeatureConstructors](interfaces/CoreCellsFeatureConstructors.md) +- [CoreColumnsFeatureConstructors](interfaces/CoreColumnsFeatureConstructors.md) +- [CoreFeatures](interfaces/CoreFeatures.md) +- [CoreHeadersFeatureConstructors](interfaces/CoreHeadersFeatureConstructors.md) +- [CoreRowModelsFeatureConstructors](interfaces/CoreRowModelsFeatureConstructors.md) +- [CoreRowsFeatureConstructors](interfaces/CoreRowsFeatureConstructors.md) +- [CoreTablesFeatureConstructors](interfaces/CoreTablesFeatureConstructors.md) +- [CreateRowModel\_Core](interfaces/CreateRowModel_Core.md) +- [CreateRowModel\_Expanded](interfaces/CreateRowModel_Expanded.md) +- [CreateRowModel\_Faceted](interfaces/CreateRowModel_Faceted.md) +- [CreateRowModel\_Filtered](interfaces/CreateRowModel_Filtered.md) +- [CreateRowModel\_Grouped](interfaces/CreateRowModel_Grouped.md) +- [CreateRowModel\_Paginated](interfaces/CreateRowModel_Paginated.md) +- [CreateRowModel\_Plugins](interfaces/CreateRowModel_Plugins.md) +- [CreateRowModel\_Sorted](interfaces/CreateRowModel_Sorted.md) +- [CreateRowModels\_Plugins](interfaces/CreateRowModels_Plugins.md) +- [FeatureConstructors](interfaces/FeatureConstructors.md) +- [FilterFn](interfaces/FilterFn.md) +- [FilterFns](interfaces/FilterFns.md) +- [FilterMeta](interfaces/FilterMeta.md) +- [GlobalFilteringFeatureConstructors](interfaces/GlobalFilteringFeatureConstructors.md) +- [Header\_ColumnResizing](interfaces/Header_ColumnResizing.md) +- [Header\_ColumnSizing](interfaces/Header_ColumnSizing.md) +- [Header\_Core](interfaces/Header_Core.md) +- [Header\_CoreProperties](interfaces/Header_CoreProperties.md) +- [Header\_Header](interfaces/Header_Header.md) +- [Header\_Plugins](interfaces/Header_Plugins.md) +- [HeaderContext](interfaces/HeaderContext.md) +- [HeaderGroup\_Core](interfaces/HeaderGroup_Core.md) +- [HeaderGroup\_Header](interfaces/HeaderGroup_Header.md) +- [HeaderGroup\_Plugins](interfaces/HeaderGroup_Plugins.md) +- [IdIdentifier](interfaces/IdIdentifier.md) +- [PaginationDefaultOptions](interfaces/PaginationDefaultOptions.md) +- [PaginationState](interfaces/PaginationState.md) +- [Plugins](interfaces/Plugins.md) +- [PrototypeAPI](interfaces/PrototypeAPI.md) +- [ResolvedColumnFilter](interfaces/ResolvedColumnFilter.md) +- [Row\_ColumnFiltering](interfaces/Row_ColumnFiltering.md) +- [Row\_ColumnGrouping](interfaces/Row_ColumnGrouping.md) +- [Row\_ColumnPinning](interfaces/Row_ColumnPinning.md) +- [Row\_ColumnVisibility](interfaces/Row_ColumnVisibility.md) +- [Row\_Core](interfaces/Row_Core.md) +- [Row\_CoreProperties](interfaces/Row_CoreProperties.md) +- [Row\_Plugins](interfaces/Row_Plugins.md) +- [Row\_Row](interfaces/Row_Row.md) +- [Row\_RowExpanding](interfaces/Row_RowExpanding.md) +- [Row\_RowPinning](interfaces/Row_RowPinning.md) +- [Row\_RowSelection](interfaces/Row_RowSelection.md) +- [RowExpandingFeatureConstructors](interfaces/RowExpandingFeatureConstructors.md) +- [RowModel](interfaces/RowModel.md) +- [RowModelFns\_ColumnFiltering](interfaces/RowModelFns_ColumnFiltering.md) +- [RowModelFns\_ColumnGrouping](interfaces/RowModelFns_ColumnGrouping.md) +- [RowModelFns\_Core](interfaces/RowModelFns_Core.md) +- [RowModelFns\_Plugins](interfaces/RowModelFns_Plugins.md) +- [RowModelFns\_RowSorting](interfaces/RowModelFns_RowSorting.md) +- [RowPaginationFeatureConstructors](interfaces/RowPaginationFeatureConstructors.md) +- [RowPinningDefaultOptions](interfaces/RowPinningDefaultOptions.md) +- [RowPinningFeatureConstructors](interfaces/RowPinningFeatureConstructors.md) +- [RowPinningState](interfaces/RowPinningState.md) +- [RowSelectionFeatureConstructors](interfaces/RowSelectionFeatureConstructors.md) +- [RowSortingFeatureConstructors](interfaces/RowSortingFeatureConstructors.md) +- [SortFn](interfaces/SortFn.md) +- [SortFns](interfaces/SortFns.md) +- [StockFeatures](interfaces/StockFeatures.md) +- [StringHeaderIdentifier](interfaces/StringHeaderIdentifier.md) +- [Table\_ColumnFaceting](interfaces/Table_ColumnFaceting.md) +- [Table\_ColumnFiltering](interfaces/Table_ColumnFiltering.md) +- [Table\_ColumnGrouping](interfaces/Table_ColumnGrouping.md) +- [Table\_ColumnOrdering](interfaces/Table_ColumnOrdering.md) +- [Table\_ColumnPinning](interfaces/Table_ColumnPinning.md) +- [Table\_ColumnResizing](interfaces/Table_ColumnResizing.md) +- [Table\_Columns](interfaces/Table_Columns.md) +- [Table\_ColumnSizing](interfaces/Table_ColumnSizing.md) +- [Table\_ColumnVisibility](interfaces/Table_ColumnVisibility.md) +- [Table\_CoreProperties](interfaces/Table_CoreProperties.md) +- [Table\_GlobalFiltering](interfaces/Table_GlobalFiltering.md) +- [Table\_Headers](interfaces/Table_Headers.md) +- [Table\_Plugins](interfaces/Table_Plugins.md) +- [Table\_RowExpanding](interfaces/Table_RowExpanding.md) +- [Table\_RowModels\_Core](interfaces/Table_RowModels_Core.md) +- [Table\_RowModels\_Expanded](interfaces/Table_RowModels_Expanded.md) +- [Table\_RowModels\_Faceted](interfaces/Table_RowModels_Faceted.md) +- [Table\_RowModels\_Filtered](interfaces/Table_RowModels_Filtered.md) +- [Table\_RowModels\_Grouped](interfaces/Table_RowModels_Grouped.md) +- [Table\_RowModels\_Paginated](interfaces/Table_RowModels_Paginated.md) +- [Table\_RowModels\_Sorted](interfaces/Table_RowModels_Sorted.md) +- [Table\_RowPagination](interfaces/Table_RowPagination.md) +- [Table\_RowPinning](interfaces/Table_RowPinning.md) +- [Table\_Rows](interfaces/Table_Rows.md) +- [Table\_RowSelection](interfaces/Table_RowSelection.md) +- [Table\_RowSorting](interfaces/Table_RowSorting.md) +- [Table\_Table](interfaces/Table_Table.md) +- [TableFeature](interfaces/TableFeature.md) +- [TableFeatures](interfaces/TableFeatures.md) +- [TableMeta](interfaces/TableMeta.md) +- [TableOptions\_Cell](interfaces/TableOptions_Cell.md) +- [TableOptions\_ColumnFiltering](interfaces/TableOptions_ColumnFiltering.md) +- [TableOptions\_ColumnGrouping](interfaces/TableOptions_ColumnGrouping.md) +- [TableOptions\_ColumnOrdering](interfaces/TableOptions_ColumnOrdering.md) +- [TableOptions\_ColumnPinning](interfaces/TableOptions_ColumnPinning.md) +- [TableOptions\_ColumnResizing](interfaces/TableOptions_ColumnResizing.md) +- [TableOptions\_Columns](interfaces/TableOptions_Columns.md) +- [TableOptions\_ColumnSizing](interfaces/TableOptions_ColumnSizing.md) +- [TableOptions\_ColumnVisibility](interfaces/TableOptions_ColumnVisibility.md) +- [TableOptions\_Core](interfaces/TableOptions_Core.md) +- [TableOptions\_GlobalFiltering](interfaces/TableOptions_GlobalFiltering.md) +- [TableOptions\_Plugins](interfaces/TableOptions_Plugins.md) +- [TableOptions\_RowExpanding](interfaces/TableOptions_RowExpanding.md) +- [TableOptions\_RowPagination](interfaces/TableOptions_RowPagination.md) +- [TableOptions\_RowPinning](interfaces/TableOptions_RowPinning.md) +- [TableOptions\_Rows](interfaces/TableOptions_Rows.md) +- [TableOptions\_RowSelection](interfaces/TableOptions_RowSelection.md) +- [TableOptions\_RowSorting](interfaces/TableOptions_RowSorting.md) +- [TableOptions\_Table](interfaces/TableOptions_Table.md) +- [TableState\_ColumnFiltering](interfaces/TableState_ColumnFiltering.md) +- [TableState\_ColumnGrouping](interfaces/TableState_ColumnGrouping.md) +- [TableState\_ColumnOrdering](interfaces/TableState_ColumnOrdering.md) +- [TableState\_ColumnPinning](interfaces/TableState_ColumnPinning.md) +- [TableState\_ColumnResizing](interfaces/TableState_ColumnResizing.md) +- [TableState\_ColumnSizing](interfaces/TableState_ColumnSizing.md) +- [TableState\_ColumnVisibility](interfaces/TableState_ColumnVisibility.md) +- [TableState\_GlobalFiltering](interfaces/TableState_GlobalFiltering.md) +- [TableState\_Plugins](interfaces/TableState_Plugins.md) +- [TableState\_RowExpanding](interfaces/TableState_RowExpanding.md) +- [TableState\_RowPagination](interfaces/TableState_RowPagination.md) +- [TableState\_RowPinning](interfaces/TableState_RowPinning.md) +- [TableState\_RowSelection](interfaces/TableState_RowSelection.md) +- [TableState\_RowSorting](interfaces/TableState_RowSorting.md) + +## Type Aliases + +- [AccessorColumnDef](type-aliases/AccessorColumnDef.md) +- [AccessorFn](type-aliases/AccessorFn.md) +- [AccessorFnColumnDef](type-aliases/AccessorFnColumnDef.md) +- [AccessorFnColumnDefBase](type-aliases/AccessorFnColumnDefBase.md) +- [AccessorKeyColumnDef](type-aliases/AccessorKeyColumnDef.md) +- [AccessorKeyColumnDefBase](type-aliases/AccessorKeyColumnDefBase.md) +- [AggregationFn](type-aliases/AggregationFn.md) +- [AggregationFnOption](type-aliases/AggregationFnOption.md) +- [APIObject](type-aliases/APIObject.md) +- [AssignCellPrototype](type-aliases/AssignCellPrototype.md) +- [AssignColumnPrototype](type-aliases/AssignColumnPrototype.md) +- [AssignHeaderPrototype](type-aliases/AssignHeaderPrototype.md) +- [AssignRowPrototype](type-aliases/AssignRowPrototype.md) +- [Atoms](type-aliases/Atoms.md) +- [Atoms\_All](type-aliases/Atoms_All.md) +- [BaseAtoms](type-aliases/BaseAtoms.md) +- [BaseAtoms\_All](type-aliases/BaseAtoms_All.md) +- [BuiltInAggregationFn](type-aliases/BuiltInAggregationFn.md) +- [BuiltInFilterFn](type-aliases/BuiltInFilterFn.md) +- [BuiltInSortFn](type-aliases/BuiltInSortFn.md) +- [CachedRowModel\_All](type-aliases/CachedRowModel_All.md) +- [CachedRowModels](type-aliases/CachedRowModels.md) +- [Cell](type-aliases/Cell.md) +- [CellData](type-aliases/CellData.md) +- [Column](type-aliases/Column.md) +- [Column\_Internal](type-aliases/Column_Internal.md) +- [ColumnDef](type-aliases/ColumnDef.md) +- [ColumnDefBase](type-aliases/ColumnDefBase.md) +- [ColumnDefBase\_All](type-aliases/ColumnDefBase_All.md) +- [ColumnDefResolved](type-aliases/ColumnDefResolved.md) +- [ColumnDefTemplate](type-aliases/ColumnDefTemplate.md) +- [ColumnFilterAutoRemoveTestFn](type-aliases/ColumnFilterAutoRemoveTestFn.md) +- [ColumnFiltersState](type-aliases/ColumnFiltersState.md) +- [ColumnHelper](type-aliases/ColumnHelper.md) +- [ColumnOrderState](type-aliases/ColumnOrderState.md) +- [ColumnPinningPosition](type-aliases/ColumnPinningPosition.md) +- [ColumnResizeDirection](type-aliases/ColumnResizeDirection.md) +- [ColumnResizeMode](type-aliases/ColumnResizeMode.md) +- [ColumnResizingDefaultOptions](type-aliases/ColumnResizingDefaultOptions.md) +- [ColumnSizingDefaultOptions](type-aliases/ColumnSizingDefaultOptions.md) +- [ColumnSizingState](type-aliases/ColumnSizingState.md) +- [ColumnVisibilityState](type-aliases/ColumnVisibilityState.md) +- [ConstructTableAPIs](type-aliases/ConstructTableAPIs.md) +- [CreateRowModels](type-aliases/CreateRowModels.md) +- [CreateRowModels\_All](type-aliases/CreateRowModels_All.md) +- [CustomAggregationFns](type-aliases/CustomAggregationFns.md) +- [CustomFilterFns](type-aliases/CustomFilterFns.md) +- [CustomSortFns](type-aliases/CustomSortFns.md) +- [DebugOptions](type-aliases/DebugOptions.md) +- [DeepKeys](type-aliases/DeepKeys.md) +- [DeepValue](type-aliases/DeepValue.md) +- [DisplayColumnDef](type-aliases/DisplayColumnDef.md) +- [ExpandedState](type-aliases/ExpandedState.md) +- [ExpandedStateList](type-aliases/ExpandedStateList.md) +- [ExternalAtoms](type-aliases/ExternalAtoms.md) +- [ExternalAtoms\_All](type-aliases/ExternalAtoms_All.md) +- [ExtractFeatureTypes](type-aliases/ExtractFeatureTypes.md) +- [FilterFnOption](type-aliases/FilterFnOption.md) +- [GetDefaultColumnDef](type-aliases/GetDefaultColumnDef.md) +- [GetDefaultStateSelector](type-aliases/GetDefaultStateSelector.md) +- [GetDefaultTableOptions](type-aliases/GetDefaultTableOptions.md) +- [GetInitialState](type-aliases/GetInitialState.md) +- [Getter](type-aliases/Getter.md) +- [GroupColumnDef](type-aliases/GroupColumnDef.md) +- [GroupingColumnMode](type-aliases/GroupingColumnMode.md) +- [GroupingState](type-aliases/GroupingState.md) +- [Header](type-aliases/Header.md) +- [HeaderGroup](type-aliases/HeaderGroup.md) +- [IdentifiedColumnDef](type-aliases/IdentifiedColumnDef.md) +- [InitRowInstanceData](type-aliases/InitRowInstanceData.md) +- [MemoFnMeta](type-aliases/MemoFnMeta.md) +- [NoInfer](type-aliases/NoInfer.md) +- [OnChangeFn](type-aliases/OnChangeFn.md) +- [PartialKeys](type-aliases/PartialKeys.md) +- [Prettify](type-aliases/Prettify.md) +- [PrototypeAPIObject](type-aliases/PrototypeAPIObject.md) +- [RequiredKeys](type-aliases/RequiredKeys.md) +- [Row](type-aliases/Row.md) +- [RowData](type-aliases/RowData.md) +- [RowModelFns](type-aliases/RowModelFns.md) +- [RowModelFns\_All](type-aliases/RowModelFns_All.md) +- [RowPinningPosition](type-aliases/RowPinningPosition.md) +- [RowSelectionState](type-aliases/RowSelectionState.md) +- [SortDirection](type-aliases/SortDirection.md) +- [SortFnOption](type-aliases/SortFnOption.md) +- [SortingState](type-aliases/SortingState.md) +- [StringOrTemplateHeader](type-aliases/StringOrTemplateHeader.md) +- [Table](type-aliases/Table.md) +- [Table\_Core](type-aliases/Table_Core.md) +- [Table\_Internal](type-aliases/Table_Internal.md) +- [Table\_RowModels](type-aliases/Table_RowModels.md) +- [TableOptions](type-aliases/TableOptions.md) +- [TableOptions\_All](type-aliases/TableOptions_All.md) +- [TableState](type-aliases/TableState.md) +- [TableState\_All](type-aliases/TableState_All.md) +- [TransformFilterValueFn](type-aliases/TransformFilterValueFn.md) +- [UnionToIntersection](type-aliases/UnionToIntersection.md) +- [Updater](type-aliases/Updater.md) +- [VisibilityDefaultOptions](type-aliases/VisibilityDefaultOptions.md) + +## Variables + +- [$internalMemoFnMeta](variables/$internalMemoFnMeta.md) +- [aggregationFn\_count](variables/aggregationFn_count.md) +- [aggregationFn\_extent](variables/aggregationFn_extent.md) +- [aggregationFn\_max](variables/aggregationFn_max.md) +- [aggregationFn\_mean](variables/aggregationFn_mean.md) +- [aggregationFn\_median](variables/aggregationFn_median.md) +- [aggregationFn\_min](variables/aggregationFn_min.md) +- [aggregationFn\_sum](variables/aggregationFn_sum.md) +- [aggregationFn\_unique](variables/aggregationFn_unique.md) +- [aggregationFn\_uniqueCount](variables/aggregationFn_uniqueCount.md) +- [aggregationFns](variables/aggregationFns.md) +- [columnFacetingFeature](variables/columnFacetingFeature.md) +- [columnFilteringFeature](variables/columnFilteringFeature.md) +- [columnGroupingFeature](variables/columnGroupingFeature.md) +- [columnOrderingFeature](variables/columnOrderingFeature.md) +- [columnPinningFeature](variables/columnPinningFeature.md) +- [columnResizingFeature](variables/columnResizingFeature.md) +- [columnSizingFeature](variables/columnSizingFeature.md) +- [columnVisibilityFeature](variables/columnVisibilityFeature.md) +- [coreCellsFeature](variables/coreCellsFeature.md) +- [coreColumnsFeature](variables/coreColumnsFeature.md) +- [coreFeatures](variables/coreFeatures.md) +- [coreHeadersFeature](variables/coreHeadersFeature.md) +- [coreRowModelsFeature](variables/coreRowModelsFeature.md) +- [coreRowsFeature](variables/coreRowsFeature.md) +- [coreTablesFeature](variables/coreTablesFeature.md) +- [filterFn\_arrHas](variables/filterFn_arrHas.md) +- [filterFn\_arrIncludes](variables/filterFn_arrIncludes.md) +- [filterFn\_arrIncludesAll](variables/filterFn_arrIncludesAll.md) +- [filterFn\_arrIncludesSome](variables/filterFn_arrIncludesSome.md) +- [filterFn\_equals](variables/filterFn_equals.md) +- [filterFn\_equalsString](variables/filterFn_equalsString.md) +- [filterFn\_equalsStringSensitive](variables/filterFn_equalsStringSensitive.md) +- [filterFn\_greaterThan](variables/filterFn_greaterThan.md) +- [filterFn\_greaterThanOrEqualTo](variables/filterFn_greaterThanOrEqualTo.md) +- [filterFn\_includesString](variables/filterFn_includesString.md) +- [filterFn\_includesStringSensitive](variables/filterFn_includesStringSensitive.md) +- [filterFn\_inNumberRange](variables/filterFn_inNumberRange.md) +- [filterFn\_lessThan](variables/filterFn_lessThan.md) +- [filterFn\_lessThanOrEqualTo](variables/filterFn_lessThanOrEqualTo.md) +- [filterFn\_weakEquals](variables/filterFn_weakEquals.md) +- [filterFns](variables/filterFns.md) +- [globalFilteringFeature](variables/globalFilteringFeature.md) +- [reSplitAlphaNumeric](variables/reSplitAlphaNumeric.md) +- [rowExpandingFeature](variables/rowExpandingFeature.md) +- [rowPaginationFeature](variables/rowPaginationFeature.md) +- [rowPinningFeature](variables/rowPinningFeature.md) +- [rowSelectionFeature](variables/rowSelectionFeature.md) +- [rowSortingFeature](variables/rowSortingFeature.md) +- [sortFn\_alphanumeric](variables/sortFn_alphanumeric.md) +- [sortFn\_alphanumericCaseSensitive](variables/sortFn_alphanumericCaseSensitive.md) +- [sortFn\_basic](variables/sortFn_basic.md) +- [sortFn\_datetime](variables/sortFn_datetime.md) +- [sortFn\_text](variables/sortFn_text.md) +- [sortFn\_textCaseSensitive](variables/sortFn_textCaseSensitive.md) +- [sortFns](variables/sortFns.md) +- [stockFeatures](variables/stockFeatures.md) + +## Functions + +- [assignPrototypeAPIs](functions/assignPrototypeAPIs.md) +- [assignTableAPIs](functions/assignTableAPIs.md) +- [buildHeaderGroups](functions/buildHeaderGroups.md) +- [callMemoOrStaticFn](functions/callMemoOrStaticFn.md) +- [cloneState](functions/cloneState.md) +- [constructCell](functions/constructCell.md) +- [constructColumn](functions/constructColumn.md) +- [constructColumnFacetingFeature](functions/constructColumnFacetingFeature.md) +- [constructColumnFilteringFeature](functions/constructColumnFilteringFeature.md) +- [constructColumnGroupingFeature](functions/constructColumnGroupingFeature.md) +- [constructColumnOrderingFeature](functions/constructColumnOrderingFeature.md) +- [constructColumnPinningFeature](functions/constructColumnPinningFeature.md) +- [constructColumnResizingFeature](functions/constructColumnResizingFeature.md) +- [constructColumnSizingFeature](functions/constructColumnSizingFeature.md) +- [constructColumnVisibilityFeature](functions/constructColumnVisibilityFeature.md) +- [constructCoreCellsFeature](functions/constructCoreCellsFeature.md) +- [constructCoreColumnsFeature](functions/constructCoreColumnsFeature.md) +- [constructCoreHeadersFeature](functions/constructCoreHeadersFeature.md) +- [constructCoreRowModelsFeature](functions/constructCoreRowModelsFeature.md) +- [constructCoreRowsFeature](functions/constructCoreRowsFeature.md) +- [constructCoreTablesFeature](functions/constructCoreTablesFeature.md) +- [constructGlobalFilteringFeature](functions/constructGlobalFilteringFeature.md) +- [constructHeader](functions/constructHeader.md) +- [constructRow](functions/constructRow.md) +- [constructRowExpandingFeature](functions/constructRowExpandingFeature.md) +- [constructRowPaginationFeature](functions/constructRowPaginationFeature.md) +- [constructRowPinningFeature](functions/constructRowPinningFeature.md) +- [constructRowSelectionFeature](functions/constructRowSelectionFeature.md) +- [constructRowSortingFeature](functions/constructRowSortingFeature.md) +- [constructTable](functions/constructTable.md) +- [createColumnHelper](functions/createColumnHelper.md) +- [createCoreRowModel](functions/createCoreRowModel.md) +- [createExpandedRowModel](functions/createExpandedRowModel.md) +- [createFacetedMinMaxValues](functions/createFacetedMinMaxValues.md) +- [createFacetedRowModel](functions/createFacetedRowModel.md) +- [createFacetedUniqueValues](functions/createFacetedUniqueValues.md) +- [createFilteredRowModel](functions/createFilteredRowModel.md) +- [createGroupedRowModel](functions/createGroupedRowModel.md) +- [createPaginatedRowModel](functions/createPaginatedRowModel.md) +- [createSortedRowModel](functions/createSortedRowModel.md) +- [expandRows](functions/expandRows.md) +- [flattenBy](functions/flattenBy.md) +- [functionalUpdate](functions/functionalUpdate.md) +- [getFunctionNameInfo](functions/getFunctionNameInfo.md) +- [getInitialTableState](functions/getInitialTableState.md) +- [getMemoFnMeta](functions/getMemoFnMeta.md) +- [isFunction](functions/isFunction.md) +- [isNumberArray](functions/isNumberArray.md) +- [makeStateUpdater](functions/makeStateUpdater.md) +- [memo](functions/memo.md) +- [noop](functions/noop.md) +- [tableFeatures](functions/tableFeatures.md) +- [tableMemo](functions/tableMemo.md) +- [tableOptions](functions/tableOptions.md) diff --git a/docs/reference/index/interfaces/API.md b/docs/reference/index/interfaces/API.md new file mode 100644 index 0000000000..2bb9dbaab6 --- /dev/null +++ b/docs/reference/index/interfaces/API.md @@ -0,0 +1,58 @@ +--- +id: API +title: API +--- + +# Interface: API\ + +Defined in: [utils.ts:331](https://github.com/TanStack/table/blob/main/packages/table-core/src/utils.ts#L331) + +## Type Parameters + +### TDeps + +`TDeps` *extends* `ReadonlyArray`\<`any`\> + +### TDepArgs + +`TDepArgs` + +## Properties + +### fn() + +```ts +fn: (...args) => any; +``` + +Defined in: [utils.ts:332](https://github.com/TanStack/table/blob/main/packages/table-core/src/utils.ts#L332) + +#### Parameters + +##### args + +...`any` + +#### Returns + +`any` + +*** + +### memoDeps()? + +```ts +optional memoDeps: (depArgs?) => any[] | undefined; +``` + +Defined in: [utils.ts:333](https://github.com/TanStack/table/blob/main/packages/table-core/src/utils.ts#L333) + +#### Parameters + +##### depArgs? + +`any` + +#### Returns + +`any`[] \| `undefined` diff --git a/docs/reference/index/interfaces/AggregationFns.md b/docs/reference/index/interfaces/AggregationFns.md new file mode 100644 index 0000000000..a342522625 --- /dev/null +++ b/docs/reference/index/interfaces/AggregationFns.md @@ -0,0 +1,8 @@ +--- +id: AggregationFns +title: AggregationFns +--- + +# Interface: AggregationFns + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:28](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L28) diff --git a/docs/reference/index/interfaces/CachedRowModel_Core.md b/docs/reference/index/interfaces/CachedRowModel_Core.md new file mode 100644 index 0000000000..3cf72ad556 --- /dev/null +++ b/docs/reference/index/interfaces/CachedRowModel_Core.md @@ -0,0 +1,36 @@ +--- +id: CachedRowModel_Core +title: CachedRowModel_Core +--- + +# Interface: CachedRowModel\_Core\ + +Defined in: [core/row-models/coreRowModelsFeature.types.ts:38](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.types.ts#L38) + +## Extends + +- [`CachedRowModel_Plugins`](CachedRowModel_Plugins.md) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### coreRowModel() + +```ts +coreRowModel: () => RowModel; +``` + +Defined in: [core/row-models/coreRowModelsFeature.types.ts:42](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.types.ts#L42) + +#### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/interfaces/CachedRowModel_Expanded.md b/docs/reference/index/interfaces/CachedRowModel_Expanded.md new file mode 100644 index 0000000000..ade90d1329 --- /dev/null +++ b/docs/reference/index/interfaces/CachedRowModel_Expanded.md @@ -0,0 +1,32 @@ +--- +id: CachedRowModel_Expanded +title: CachedRowModel_Expanded +--- + +# Interface: CachedRowModel\_Expanded\ + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:141](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L141) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### expandedRowModel() + +```ts +expandedRowModel: () => RowModel; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:145](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L145) + +#### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/interfaces/CachedRowModel_Faceted.md b/docs/reference/index/interfaces/CachedRowModel_Faceted.md new file mode 100644 index 0000000000..24399c09e5 --- /dev/null +++ b/docs/reference/index/interfaces/CachedRowModel_Faceted.md @@ -0,0 +1,84 @@ +--- +id: CachedRowModel_Faceted +title: CachedRowModel_Faceted +--- + +# Interface: CachedRowModel\_Faceted\ + +Defined in: [features/column-faceting/columnFacetingFeature.types.ts:78](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.types.ts#L78) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### facetedMinMaxValues()? + +```ts +optional facetedMinMaxValues: (columnId) => [number, number]; +``` + +Defined in: [features/column-faceting/columnFacetingFeature.types.ts:83](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.types.ts#L83) + +#### Parameters + +##### columnId + +`string` + +#### Returns + +\[`number`, `number`\] + +*** + +### facetedRowModel()? + +```ts +optional facetedRowModel: (columnId) => () => RowModel; +``` + +Defined in: [features/column-faceting/columnFacetingFeature.types.ts:82](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.types.ts#L82) + +#### Parameters + +##### columnId + +`string` + +#### Returns + +```ts +(): RowModel; +``` + +##### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> + +*** + +### facetedUniqueValues()? + +```ts +optional facetedUniqueValues: (columnId) => Map; +``` + +Defined in: [features/column-faceting/columnFacetingFeature.types.ts:84](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.types.ts#L84) + +#### Parameters + +##### columnId + +`string` + +#### Returns + +`Map`\<`any`, `number`\> diff --git a/docs/reference/index/interfaces/CachedRowModel_Filtered.md b/docs/reference/index/interfaces/CachedRowModel_Filtered.md new file mode 100644 index 0000000000..ffc38cc623 --- /dev/null +++ b/docs/reference/index/interfaces/CachedRowModel_Filtered.md @@ -0,0 +1,32 @@ +--- +id: CachedRowModel_Filtered +title: CachedRowModel_Filtered +--- + +# Interface: CachedRowModel\_Filtered\ + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:217](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L217) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### filteredRowModel() + +```ts +filteredRowModel: () => RowModel; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:221](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L221) + +#### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/interfaces/CachedRowModel_Grouped.md b/docs/reference/index/interfaces/CachedRowModel_Grouped.md new file mode 100644 index 0000000000..0c1af7ccde --- /dev/null +++ b/docs/reference/index/interfaces/CachedRowModel_Grouped.md @@ -0,0 +1,32 @@ +--- +id: CachedRowModel_Grouped +title: CachedRowModel_Grouped +--- + +# Interface: CachedRowModel\_Grouped\ + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:217](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L217) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### groupedRowModel() + +```ts +groupedRowModel: () => RowModel; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:221](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L221) + +#### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/interfaces/CachedRowModel_Paginated.md b/docs/reference/index/interfaces/CachedRowModel_Paginated.md new file mode 100644 index 0000000000..00985ee40e --- /dev/null +++ b/docs/reference/index/interfaces/CachedRowModel_Paginated.md @@ -0,0 +1,32 @@ +--- +id: CachedRowModel_Paginated +title: CachedRowModel_Paginated +--- + +# Interface: CachedRowModel\_Paginated\ + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:140](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L140) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### paginatedRowModel() + +```ts +paginatedRowModel: () => RowModel; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:144](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L144) + +#### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/interfaces/CachedRowModel_Plugins.md b/docs/reference/index/interfaces/CachedRowModel_Plugins.md new file mode 100644 index 0000000000..4a4bc510b6 --- /dev/null +++ b/docs/reference/index/interfaces/CachedRowModel_Plugins.md @@ -0,0 +1,12 @@ +--- +id: CachedRowModel_Plugins +title: CachedRowModel_Plugins +--- + +# Interface: CachedRowModel\_Plugins + +Defined in: [core/row-models/coreRowModelsFeature.types.ts:36](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.types.ts#L36) + +## Extended by + +- [`CachedRowModel_Core`](CachedRowModel_Core.md) diff --git a/docs/reference/index/interfaces/CachedRowModel_Sorted.md b/docs/reference/index/interfaces/CachedRowModel_Sorted.md new file mode 100644 index 0000000000..9e7ca135c8 --- /dev/null +++ b/docs/reference/index/interfaces/CachedRowModel_Sorted.md @@ -0,0 +1,32 @@ +--- +id: CachedRowModel_Sorted +title: CachedRowModel_Sorted +--- + +# Interface: CachedRowModel\_Sorted\ + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:228](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L228) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### sortedRowModel() + +```ts +sortedRowModel: () => RowModel; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:232](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L232) + +#### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/interfaces/CachedRowModels_Plugins.md b/docs/reference/index/interfaces/CachedRowModels_Plugins.md new file mode 100644 index 0000000000..178e9a9cfc --- /dev/null +++ b/docs/reference/index/interfaces/CachedRowModels_Plugins.md @@ -0,0 +1,18 @@ +--- +id: CachedRowModels_Plugins +title: CachedRowModels_Plugins +--- + +# Interface: CachedRowModels\_Plugins\ + +Defined in: [types/RowModel.ts:87](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/RowModel.ts#L87) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/CellContext.md b/docs/reference/index/interfaces/CellContext.md new file mode 100644 index 0000000000..cddb9a6e7c --- /dev/null +++ b/docs/reference/index/interfaces/CellContext.md @@ -0,0 +1,82 @@ +--- +id: CellContext +title: CellContext +--- + +# Interface: CellContext\ + +Defined in: [core/cells/coreCellsFeature.types.ts:8](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L8) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](../type-aliases/CellData.md) = [`CellData`](../type-aliases/CellData.md) + +## Properties + +### cell + +```ts +cell: Cell; +``` + +Defined in: [core/cells/coreCellsFeature.types.ts:13](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L13) + +*** + +### column + +```ts +column: Column; +``` + +Defined in: [core/cells/coreCellsFeature.types.ts:14](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L14) + +*** + +### getValue + +```ts +getValue: Getter; +``` + +Defined in: [core/cells/coreCellsFeature.types.ts:15](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L15) + +*** + +### renderValue + +```ts +renderValue: Getter; +``` + +Defined in: [core/cells/coreCellsFeature.types.ts:16](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L16) + +*** + +### row + +```ts +row: Row; +``` + +Defined in: [core/cells/coreCellsFeature.types.ts:17](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L17) + +*** + +### table + +```ts +table: Table; +``` + +Defined in: [core/cells/coreCellsFeature.types.ts:18](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L18) diff --git a/docs/reference/index/interfaces/Cell_Cell.md b/docs/reference/index/interfaces/Cell_Cell.md new file mode 100644 index 0000000000..e2fe107a6b --- /dev/null +++ b/docs/reference/index/interfaces/Cell_Cell.md @@ -0,0 +1,134 @@ +--- +id: Cell_Cell +title: Cell_Cell +--- + +# Interface: Cell\_Cell\ + +Defined in: [core/cells/coreCellsFeature.types.ts:44](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L44) + +## Extends + +- [`Cell_CoreProperties`](Cell_CoreProperties.md)\<`TFeatures`, `TData`, `TValue`\> + +## Extended by + +- [`Cell_Core`](Cell_Core.md) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](../type-aliases/CellData.md) = [`CellData`](../type-aliases/CellData.md) + +## Properties + +### column + +```ts +column: Column; +``` + +Defined in: [core/cells/coreCellsFeature.types.ts:29](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L29) + +The associated Column object for the cell. + +#### Inherited from + +[`Cell_CoreProperties`](Cell_CoreProperties.md).[`column`](Cell_CoreProperties.md#column) + +*** + +### getContext() + +```ts +getContext: () => CellContext; +``` + +Defined in: [core/cells/coreCellsFeature.types.ts:52](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L52) + +Returns the rendering context (or props) for cell-based components like cells and aggregated cells. Use these props with your framework's `flexRender` utility to render these using the template of your choice: + +#### Returns + +[`CellContext`](CellContext.md)\<`TFeatures`, `TData`, `TValue`\> + +*** + +### getValue + +```ts +getValue: Getter; +``` + +Defined in: [core/cells/coreCellsFeature.types.ts:56](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L56) + +Returns the value for the cell, accessed via the associated column's accessor key or accessor function. + +*** + +### id + +```ts +id: string; +``` + +Defined in: [core/cells/coreCellsFeature.types.ts:33](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L33) + +The unique ID for the cell across the entire table. + +#### Inherited from + +[`Cell_CoreProperties`](Cell_CoreProperties.md).[`id`](Cell_CoreProperties.md#id) + +*** + +### renderValue + +```ts +renderValue: Getter; +``` + +Defined in: [core/cells/coreCellsFeature.types.ts:60](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L60) + +Renders the value for a cell the same as `getValue`, but will return the `renderFallbackValue` if no value is found. + +*** + +### row + +```ts +row: Row; +``` + +Defined in: [core/cells/coreCellsFeature.types.ts:37](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L37) + +The associated Row object for the cell. + +#### Inherited from + +[`Cell_CoreProperties`](Cell_CoreProperties.md).[`row`](Cell_CoreProperties.md#row) + +*** + +### table + +```ts +table: Table_Internal; +``` + +Defined in: [core/cells/coreCellsFeature.types.ts:41](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L41) + +Reference to the parent table instance. + +#### Inherited from + +[`Cell_CoreProperties`](Cell_CoreProperties.md).[`table`](Cell_CoreProperties.md#table) diff --git a/docs/reference/index/interfaces/Cell_ColumnGrouping.md b/docs/reference/index/interfaces/Cell_ColumnGrouping.md new file mode 100644 index 0000000000..e4b7682d8f --- /dev/null +++ b/docs/reference/index/interfaces/Cell_ColumnGrouping.md @@ -0,0 +1,56 @@ +--- +id: Cell_ColumnGrouping +title: Cell_ColumnGrouping +--- + +# Interface: Cell\_ColumnGrouping + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:132](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L132) + +## Properties + +### getIsAggregated() + +```ts +getIsAggregated: () => boolean; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:136](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L136) + +Returns whether or not the cell is currently aggregated. + +#### Returns + +`boolean` + +*** + +### getIsGrouped() + +```ts +getIsGrouped: () => boolean; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:140](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L140) + +Returns whether or not the cell is currently grouped. + +#### Returns + +`boolean` + +*** + +### getIsPlaceholder() + +```ts +getIsPlaceholder: () => boolean; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:144](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L144) + +Returns whether or not the cell is currently a placeholder cell. + +#### Returns + +`boolean` diff --git a/docs/reference/index/interfaces/Cell_Core.md b/docs/reference/index/interfaces/Cell_Core.md new file mode 100644 index 0000000000..fa1c036d2e --- /dev/null +++ b/docs/reference/index/interfaces/Cell_Core.md @@ -0,0 +1,142 @@ +--- +id: Cell_Core +title: Cell_Core +--- + +# Interface: Cell\_Core\ + +Defined in: [types/Cell.ts:16](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Cell.ts#L16) + +## Extends + +- [`Cell_Cell`](Cell_Cell.md)\<`TFeatures`, `TData`, `TValue`\> + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](../type-aliases/CellData.md) = [`CellData`](../type-aliases/CellData.md) + +## Properties + +### column + +```ts +column: Column; +``` + +Defined in: [core/cells/coreCellsFeature.types.ts:29](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L29) + +The associated Column object for the cell. + +#### Inherited from + +[`Cell_Cell`](Cell_Cell.md).[`column`](Cell_Cell.md#column) + +*** + +### getContext() + +```ts +getContext: () => CellContext; +``` + +Defined in: [core/cells/coreCellsFeature.types.ts:52](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L52) + +Returns the rendering context (or props) for cell-based components like cells and aggregated cells. Use these props with your framework's `flexRender` utility to render these using the template of your choice: + +#### Returns + +[`CellContext`](CellContext.md)\<`TFeatures`, `TData`, `TValue`\> + +#### Inherited from + +[`Cell_Cell`](Cell_Cell.md).[`getContext`](Cell_Cell.md#getcontext) + +*** + +### getValue + +```ts +getValue: Getter; +``` + +Defined in: [core/cells/coreCellsFeature.types.ts:56](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L56) + +Returns the value for the cell, accessed via the associated column's accessor key or accessor function. + +#### Inherited from + +[`Cell_Cell`](Cell_Cell.md).[`getValue`](Cell_Cell.md#getvalue) + +*** + +### id + +```ts +id: string; +``` + +Defined in: [core/cells/coreCellsFeature.types.ts:33](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L33) + +The unique ID for the cell across the entire table. + +#### Inherited from + +[`Cell_Cell`](Cell_Cell.md).[`id`](Cell_Cell.md#id) + +*** + +### renderValue + +```ts +renderValue: Getter; +``` + +Defined in: [core/cells/coreCellsFeature.types.ts:60](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L60) + +Renders the value for a cell the same as `getValue`, but will return the `renderFallbackValue` if no value is found. + +#### Inherited from + +[`Cell_Cell`](Cell_Cell.md).[`renderValue`](Cell_Cell.md#rendervalue) + +*** + +### row + +```ts +row: Row; +``` + +Defined in: [core/cells/coreCellsFeature.types.ts:37](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L37) + +The associated Row object for the cell. + +#### Inherited from + +[`Cell_Cell`](Cell_Cell.md).[`row`](Cell_Cell.md#row) + +*** + +### table + +```ts +table: Table_Internal; +``` + +Defined in: [core/cells/coreCellsFeature.types.ts:41](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L41) + +Reference to the parent table instance. + +#### Inherited from + +[`Cell_Cell`](Cell_Cell.md).[`table`](Cell_Cell.md#table) diff --git a/docs/reference/index/interfaces/Cell_CoreProperties.md b/docs/reference/index/interfaces/Cell_CoreProperties.md new file mode 100644 index 0000000000..67a3293126 --- /dev/null +++ b/docs/reference/index/interfaces/Cell_CoreProperties.md @@ -0,0 +1,74 @@ +--- +id: Cell_CoreProperties +title: Cell_CoreProperties +--- + +# Interface: Cell\_CoreProperties\ + +Defined in: [core/cells/coreCellsFeature.types.ts:21](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L21) + +## Extended by + +- [`Cell_Cell`](Cell_Cell.md) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](../type-aliases/CellData.md) = [`CellData`](../type-aliases/CellData.md) + +## Properties + +### column + +```ts +column: Column; +``` + +Defined in: [core/cells/coreCellsFeature.types.ts:29](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L29) + +The associated Column object for the cell. + +*** + +### id + +```ts +id: string; +``` + +Defined in: [core/cells/coreCellsFeature.types.ts:33](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L33) + +The unique ID for the cell across the entire table. + +*** + +### row + +```ts +row: Row; +``` + +Defined in: [core/cells/coreCellsFeature.types.ts:37](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L37) + +The associated Row object for the cell. + +*** + +### table + +```ts +table: Table_Internal; +``` + +Defined in: [core/cells/coreCellsFeature.types.ts:41](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L41) + +Reference to the parent table instance. diff --git a/docs/reference/index/interfaces/Cell_Plugins.md b/docs/reference/index/interfaces/Cell_Plugins.md new file mode 100644 index 0000000000..a751054438 --- /dev/null +++ b/docs/reference/index/interfaces/Cell_Plugins.md @@ -0,0 +1,25 @@ +--- +id: Cell_Plugins +title: Cell_Plugins +--- + +# Interface: Cell\_Plugins\ + +Defined in: [types/Cell.ts:10](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Cell.ts#L10) + +Use this interface as a target for declaration merging to add your own plugin properties. +Note: This will affect the types of all tables in your project. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](../type-aliases/CellData.md) = [`CellData`](../type-aliases/CellData.md) diff --git a/docs/reference/index/interfaces/ColumnDef_ColumnFiltering.md b/docs/reference/index/interfaces/ColumnDef_ColumnFiltering.md new file mode 100644 index 0000000000..3128dc63b7 --- /dev/null +++ b/docs/reference/index/interfaces/ColumnDef_ColumnFiltering.md @@ -0,0 +1,42 @@ +--- +id: ColumnDef_ColumnFiltering +title: ColumnDef_ColumnFiltering +--- + +# Interface: ColumnDef\_ColumnFiltering\ + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:81](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L81) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### enableColumnFilter? + +```ts +optional enableColumnFilter: boolean; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:88](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L88) + +Enables/disables the **column** filter for this column. + +*** + +### filterFn? + +```ts +optional filterFn: FilterFnOption; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:92](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L92) + +The filter function to use with this column. Can be the name of a built-in filter function or a custom filter function. diff --git a/docs/reference/index/interfaces/ColumnDef_ColumnGrouping.md b/docs/reference/index/interfaces/ColumnDef_ColumnGrouping.md new file mode 100644 index 0000000000..e1d57d7f83 --- /dev/null +++ b/docs/reference/index/interfaces/ColumnDef_ColumnGrouping.md @@ -0,0 +1,80 @@ +--- +id: ColumnDef_ColumnGrouping +title: ColumnDef_ColumnGrouping +--- + +# Interface: ColumnDef\_ColumnGrouping\ + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:53](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L53) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](../type-aliases/CellData.md) = [`CellData`](../type-aliases/CellData.md) + +## Properties + +### aggregatedCell? + +```ts +optional aggregatedCell: ColumnDefTemplate["getContext"]>>; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:61](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L61) + +The cell to display each row for the column if the cell is an aggregate. If a function is passed, it will be passed a props object with the context of the cell and should return the property type for your adapter (the exact type depends on the adapter being used). + +*** + +### aggregationFn? + +```ts +optional aggregationFn: AggregationFnOption; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:67](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L67) + +The resolved aggregation function for the column. + +*** + +### enableGrouping? + +```ts +optional enableGrouping: boolean; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:71](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L71) + +Enables/disables grouping for this column. + +*** + +### getGroupingValue()? + +```ts +optional getGroupingValue: (row) => any; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:75](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L75) + +Specify a value to be used for grouping rows on this column. If this option is not specified, the value derived from `accessorKey` / `accessorFn` will be used instead. + +#### Parameters + +##### row + +`TData` + +#### Returns + +`any` diff --git a/docs/reference/index/interfaces/ColumnDef_ColumnPinning.md b/docs/reference/index/interfaces/ColumnDef_ColumnPinning.md new file mode 100644 index 0000000000..a14df45819 --- /dev/null +++ b/docs/reference/index/interfaces/ColumnDef_ColumnPinning.md @@ -0,0 +1,20 @@ +--- +id: ColumnDef_ColumnPinning +title: ColumnDef_ColumnPinning +--- + +# Interface: ColumnDef\_ColumnPinning + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:36](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L36) + +## Properties + +### enablePinning? + +```ts +optional enablePinning: boolean; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:40](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L40) + +Enables/disables column pinning for this column. Defaults to `true`. diff --git a/docs/reference/index/interfaces/ColumnDef_ColumnResizing.md b/docs/reference/index/interfaces/ColumnDef_ColumnResizing.md new file mode 100644 index 0000000000..3a99ef6bd2 --- /dev/null +++ b/docs/reference/index/interfaces/ColumnDef_ColumnResizing.md @@ -0,0 +1,20 @@ +--- +id: ColumnDef_ColumnResizing +title: ColumnDef_ColumnResizing +--- + +# Interface: ColumnDef\_ColumnResizing + +Defined in: [features/column-resizing/columnResizingFeature.types.ts:63](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts#L63) + +## Properties + +### enableResizing? + +```ts +optional enableResizing: boolean; +``` + +Defined in: [features/column-resizing/columnResizingFeature.types.ts:67](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts#L67) + +Enables or disables column resizing for the column. diff --git a/docs/reference/index/interfaces/ColumnDef_ColumnSizing.md b/docs/reference/index/interfaces/ColumnDef_ColumnSizing.md new file mode 100644 index 0000000000..644a49a293 --- /dev/null +++ b/docs/reference/index/interfaces/ColumnDef_ColumnSizing.md @@ -0,0 +1,44 @@ +--- +id: ColumnDef_ColumnSizing +title: ColumnDef_ColumnSizing +--- + +# Interface: ColumnDef\_ColumnSizing + +Defined in: [features/column-sizing/columnSizingFeature.types.ts:52](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.types.ts#L52) + +## Properties + +### maxSize? + +```ts +optional maxSize: number; +``` + +Defined in: [features/column-sizing/columnSizingFeature.types.ts:56](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.types.ts#L56) + +The maximum allowed size for the column + +*** + +### minSize? + +```ts +optional minSize: number; +``` + +Defined in: [features/column-sizing/columnSizingFeature.types.ts:60](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.types.ts#L60) + +The minimum allowed size for the column + +*** + +### size? + +```ts +optional size: number; +``` + +Defined in: [features/column-sizing/columnSizingFeature.types.ts:64](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.types.ts#L64) + +The desired size for the column diff --git a/docs/reference/index/interfaces/ColumnDef_ColumnVisibility.md b/docs/reference/index/interfaces/ColumnDef_ColumnVisibility.md new file mode 100644 index 0000000000..5c4a8cbc3c --- /dev/null +++ b/docs/reference/index/interfaces/ColumnDef_ColumnVisibility.md @@ -0,0 +1,20 @@ +--- +id: ColumnDef_ColumnVisibility +title: ColumnDef_ColumnVisibility +--- + +# Interface: ColumnDef\_ColumnVisibility + +Defined in: [features/column-visibility/columnVisibilityFeature.types.ts:68](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.types.ts#L68) + +## Properties + +### enableHiding? + +```ts +optional enableHiding: boolean; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.types.ts:72](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.types.ts#L72) + +Enables/disables column hiding for this column. Defaults to `true`. diff --git a/docs/reference/index/interfaces/ColumnDef_GlobalFiltering.md b/docs/reference/index/interfaces/ColumnDef_GlobalFiltering.md new file mode 100644 index 0000000000..b55f87b831 --- /dev/null +++ b/docs/reference/index/interfaces/ColumnDef_GlobalFiltering.md @@ -0,0 +1,20 @@ +--- +id: ColumnDef_GlobalFiltering +title: ColumnDef_GlobalFiltering +--- + +# Interface: ColumnDef\_GlobalFiltering + +Defined in: [features/global-filtering/globalFilteringFeature.types.ts:18](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.types.ts#L18) + +## Properties + +### enableGlobalFilter? + +```ts +optional enableGlobalFilter: boolean; +``` + +Defined in: [features/global-filtering/globalFilteringFeature.types.ts:22](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.types.ts#L22) + +Enables/disables the **global** filter for this column. diff --git a/docs/reference/index/interfaces/ColumnDef_Plugins.md b/docs/reference/index/interfaces/ColumnDef_Plugins.md new file mode 100644 index 0000000000..0f739f438f --- /dev/null +++ b/docs/reference/index/interfaces/ColumnDef_Plugins.md @@ -0,0 +1,25 @@ +--- +id: ColumnDef_Plugins +title: ColumnDef_Plugins +--- + +# Interface: ColumnDef\_Plugins\ + +Defined in: [types/ColumnDef.ts:18](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L18) + +Use this interface as a target for declaration merging to add your own plugin properties. +Note: This will affect the types of all tables in your project. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](../type-aliases/CellData.md) = [`CellData`](../type-aliases/CellData.md) diff --git a/docs/reference/index/interfaces/ColumnDef_RowSorting.md b/docs/reference/index/interfaces/ColumnDef_RowSorting.md new file mode 100644 index 0000000000..c4d521db93 --- /dev/null +++ b/docs/reference/index/interfaces/ColumnDef_RowSorting.md @@ -0,0 +1,98 @@ +--- +id: ColumnDef_RowSorting +title: ColumnDef_RowSorting +--- + +# Interface: ColumnDef\_RowSorting\ + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:51](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L51) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### enableMultiSort? + +```ts +optional enableMultiSort: boolean; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:58](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L58) + +Enables/Disables multi-sorting for this column. + +*** + +### enableSorting? + +```ts +optional enableSorting: boolean; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:62](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L62) + +Enables/Disables sorting for this column. + +*** + +### invertSorting? + +```ts +optional invertSorting: boolean; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:66](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L66) + +Inverts the order of the sorting for this column. This is useful for values that have an inverted best/worst scale where lower numbers are better, eg. a ranking (1st, 2nd, 3rd) or golf-like scoring + +*** + +### sortDescFirst? + +```ts +optional sortDescFirst: boolean; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:70](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L70) + +Set to `true` for sorting toggles on this column to start in the descending direction. + +*** + +### sortFn? + +```ts +optional sortFn: SortFnOption; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:76](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L76) + +The sorting function to use with this column. +- A `string` referencing a built-in sorting function +- A custom sorting function + +*** + +### sortUndefined? + +```ts +optional sortUndefined: false | 1 | -1 | "first" | "last"; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:86](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L86) + +The priority of undefined values when sorting this column. +- `false` + - Undefined values will be considered tied and need to be sorted by the next column filter or original index (whichever applies) +- `-1` + - Undefined values will be sorted with higher priority (ascending) (if ascending, undefined will appear on the beginning of the list) +- `1` + - Undefined values will be sorted with lower priority (descending) (if ascending, undefined will appear on the end of the list) diff --git a/docs/reference/index/interfaces/ColumnDefaultOptions.md b/docs/reference/index/interfaces/ColumnDefaultOptions.md new file mode 100644 index 0000000000..9acb1cbd8d --- /dev/null +++ b/docs/reference/index/interfaces/ColumnDefaultOptions.md @@ -0,0 +1,28 @@ +--- +id: ColumnDefaultOptions +title: ColumnDefaultOptions +--- + +# Interface: ColumnDefaultOptions + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:147](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L147) + +## Properties + +### enableGrouping + +```ts +enableGrouping: boolean; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:148](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L148) + +*** + +### onGroupingChange + +```ts +onGroupingChange: OnChangeFn; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:149](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L149) diff --git a/docs/reference/index/interfaces/ColumnFacetingFeatureConstructors.md b/docs/reference/index/interfaces/ColumnFacetingFeatureConstructors.md new file mode 100644 index 0000000000..1acef999c6 --- /dev/null +++ b/docs/reference/index/interfaces/ColumnFacetingFeatureConstructors.md @@ -0,0 +1,18 @@ +--- +id: ColumnFacetingFeatureConstructors +title: ColumnFacetingFeatureConstructors +--- + +# Interface: ColumnFacetingFeatureConstructors\ + +Defined in: [features/column-faceting/columnFacetingFeature.ts:23](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.ts#L23) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/ColumnFilter.md b/docs/reference/index/interfaces/ColumnFilter.md new file mode 100644 index 0000000000..d19677e9c5 --- /dev/null +++ b/docs/reference/index/interfaces/ColumnFilter.md @@ -0,0 +1,28 @@ +--- +id: ColumnFilter +title: ColumnFilter +--- + +# Interface: ColumnFilter + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L24) + +## Properties + +### id + +```ts +id: string; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:25](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L25) + +*** + +### value + +```ts +value: unknown; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:26](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L26) diff --git a/docs/reference/index/interfaces/ColumnFilteringFeatureConstructors.md b/docs/reference/index/interfaces/ColumnFilteringFeatureConstructors.md new file mode 100644 index 0000000000..83e2b2ed46 --- /dev/null +++ b/docs/reference/index/interfaces/ColumnFilteringFeatureConstructors.md @@ -0,0 +1,18 @@ +--- +id: ColumnFilteringFeatureConstructors +title: ColumnFilteringFeatureConstructors +--- + +# Interface: ColumnFilteringFeatureConstructors\ + +Defined in: [features/column-filtering/columnFilteringFeature.ts:32](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.ts#L32) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/ColumnGroupingFeatureConstructors.md b/docs/reference/index/interfaces/ColumnGroupingFeatureConstructors.md new file mode 100644 index 0000000000..5a879a3f78 --- /dev/null +++ b/docs/reference/index/interfaces/ColumnGroupingFeatureConstructors.md @@ -0,0 +1,18 @@ +--- +id: ColumnGroupingFeatureConstructors +title: ColumnGroupingFeatureConstructors +--- + +# Interface: ColumnGroupingFeatureConstructors\ + +Defined in: [features/column-grouping/columnGroupingFeature.ts:38](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.ts#L38) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/ColumnMeta.md b/docs/reference/index/interfaces/ColumnMeta.md new file mode 100644 index 0000000000..9ecede6d4e --- /dev/null +++ b/docs/reference/index/interfaces/ColumnMeta.md @@ -0,0 +1,22 @@ +--- +id: ColumnMeta +title: ColumnMeta +--- + +# Interface: ColumnMeta\ + +Defined in: [types/ColumnDef.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L24) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](../type-aliases/CellData.md) = [`CellData`](../type-aliases/CellData.md) diff --git a/docs/reference/index/interfaces/ColumnOrderDefaultOptions.md b/docs/reference/index/interfaces/ColumnOrderDefaultOptions.md new file mode 100644 index 0000000000..c6aad9f588 --- /dev/null +++ b/docs/reference/index/interfaces/ColumnOrderDefaultOptions.md @@ -0,0 +1,18 @@ +--- +id: ColumnOrderDefaultOptions +title: ColumnOrderDefaultOptions +--- + +# Interface: ColumnOrderDefaultOptions + +Defined in: [features/column-ordering/columnOrderingFeature.types.ts:35](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-ordering/columnOrderingFeature.types.ts#L35) + +## Properties + +### onColumnOrderChange + +```ts +onColumnOrderChange: OnChangeFn; +``` + +Defined in: [features/column-ordering/columnOrderingFeature.types.ts:36](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-ordering/columnOrderingFeature.types.ts#L36) diff --git a/docs/reference/index/interfaces/ColumnOrderingFeatureConstructors.md b/docs/reference/index/interfaces/ColumnOrderingFeatureConstructors.md new file mode 100644 index 0000000000..16b3c9006d --- /dev/null +++ b/docs/reference/index/interfaces/ColumnOrderingFeatureConstructors.md @@ -0,0 +1,18 @@ +--- +id: ColumnOrderingFeatureConstructors +title: ColumnOrderingFeatureConstructors +--- + +# Interface: ColumnOrderingFeatureConstructors\ + +Defined in: [features/column-ordering/columnOrderingFeature.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-ordering/columnOrderingFeature.ts#L24) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/ColumnPinningDefaultOptions.md b/docs/reference/index/interfaces/ColumnPinningDefaultOptions.md new file mode 100644 index 0000000000..80af006f74 --- /dev/null +++ b/docs/reference/index/interfaces/ColumnPinningDefaultOptions.md @@ -0,0 +1,18 @@ +--- +id: ColumnPinningDefaultOptions +title: ColumnPinningDefaultOptions +--- + +# Interface: ColumnPinningDefaultOptions + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:32](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L32) + +## Properties + +### onColumnPinningChange + +```ts +onColumnPinningChange: OnChangeFn; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:33](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L33) diff --git a/docs/reference/index/interfaces/ColumnPinningFeatureConstructors.md b/docs/reference/index/interfaces/ColumnPinningFeatureConstructors.md new file mode 100644 index 0000000000..2f33d027c9 --- /dev/null +++ b/docs/reference/index/interfaces/ColumnPinningFeatureConstructors.md @@ -0,0 +1,18 @@ +--- +id: ColumnPinningFeatureConstructors +title: ColumnPinningFeatureConstructors +--- + +# Interface: ColumnPinningFeatureConstructors\ + +Defined in: [features/column-pinning/columnPinningFeature.ts:52](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.ts#L52) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/ColumnPinningState.md b/docs/reference/index/interfaces/ColumnPinningState.md new file mode 100644 index 0000000000..7e30ac30ab --- /dev/null +++ b/docs/reference/index/interfaces/ColumnPinningState.md @@ -0,0 +1,28 @@ +--- +id: ColumnPinningState +title: ColumnPinningState +--- + +# Interface: ColumnPinningState + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:10](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L10) + +## Properties + +### left + +```ts +left: string[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:11](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L11) + +*** + +### right + +```ts +right: string[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:12](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L12) diff --git a/docs/reference/index/interfaces/ColumnResizingFeatureConstructors.md b/docs/reference/index/interfaces/ColumnResizingFeatureConstructors.md new file mode 100644 index 0000000000..8818edd5f1 --- /dev/null +++ b/docs/reference/index/interfaces/ColumnResizingFeatureConstructors.md @@ -0,0 +1,18 @@ +--- +id: ColumnResizingFeatureConstructors +title: ColumnResizingFeatureConstructors +--- + +# Interface: ColumnResizingFeatureConstructors\ + +Defined in: [features/column-resizing/columnResizingFeature.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.ts#L24) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/ColumnSizingFeatureConstructors.md b/docs/reference/index/interfaces/ColumnSizingFeatureConstructors.md new file mode 100644 index 0000000000..7302cc839b --- /dev/null +++ b/docs/reference/index/interfaces/ColumnSizingFeatureConstructors.md @@ -0,0 +1,18 @@ +--- +id: ColumnSizingFeatureConstructors +title: ColumnSizingFeatureConstructors +--- + +# Interface: ColumnSizingFeatureConstructors\ + +Defined in: [features/column-sizing/columnSizingFeature.ts:35](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.ts#L35) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/ColumnSort.md b/docs/reference/index/interfaces/ColumnSort.md new file mode 100644 index 0000000000..0737a1eba0 --- /dev/null +++ b/docs/reference/index/interfaces/ColumnSort.md @@ -0,0 +1,28 @@ +--- +id: ColumnSort +title: ColumnSort +--- + +# Interface: ColumnSort + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:10](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L10) + +## Properties + +### desc + +```ts +desc: boolean; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:11](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L11) + +*** + +### id + +```ts +id: string; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:12](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L12) diff --git a/docs/reference/index/interfaces/ColumnVisibilityFeatureConstructors.md b/docs/reference/index/interfaces/ColumnVisibilityFeatureConstructors.md new file mode 100644 index 0000000000..0e388c6809 --- /dev/null +++ b/docs/reference/index/interfaces/ColumnVisibilityFeatureConstructors.md @@ -0,0 +1,18 @@ +--- +id: ColumnVisibilityFeatureConstructors +title: ColumnVisibilityFeatureConstructors +--- + +# Interface: ColumnVisibilityFeatureConstructors\ + +Defined in: [features/column-visibility/columnVisibilityFeature.ts:39](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.ts#L39) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/Column_Column.md b/docs/reference/index/interfaces/Column_Column.md new file mode 100644 index 0000000000..64e4ed69d8 --- /dev/null +++ b/docs/reference/index/interfaces/Column_Column.md @@ -0,0 +1,177 @@ +--- +id: Column_Column +title: Column_Column +--- + +# Interface: Column\_Column\ + +Defined in: [core/columns/coreColumnsFeature.types.ts:45](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L45) + +## Extends + +- [`Column_CoreProperties`](Column_CoreProperties.md)\<`TFeatures`, `TData`, `TValue`\> + +## Extended by + +- [`Column_Core`](Column_Core.md) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](../type-aliases/CellData.md) = [`CellData`](../type-aliases/CellData.md) + +## Properties + +### accessorFn? + +```ts +optional accessorFn: AccessorFn; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:15](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L15) + +The resolved accessor function to use when extracting the value for the column from each row. Will only be defined if the column def has a valid accessor key or function defined. + +#### Inherited from + +[`Column_CoreProperties`](Column_CoreProperties.md).[`accessorFn`](Column_CoreProperties.md#accessorfn) + +*** + +### columnDef + +```ts +columnDef: ColumnDef; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:19](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L19) + +The original column def used to create the column. + +#### Inherited from + +[`Column_CoreProperties`](Column_CoreProperties.md).[`columnDef`](Column_CoreProperties.md#columndef) + +*** + +### columns + +```ts +columns: Column[]; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:23](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L23) + +The child column (if the column is a group column). Will be an empty array if the column is not a group column. + +#### Inherited from + +[`Column_CoreProperties`](Column_CoreProperties.md).[`columns`](Column_CoreProperties.md#columns) + +*** + +### depth + +```ts +depth: number; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:27](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L27) + +The depth of the column (if grouped) relative to the root column def array. + +#### Inherited from + +[`Column_CoreProperties`](Column_CoreProperties.md).[`depth`](Column_CoreProperties.md#depth) + +*** + +### getFlatColumns() + +```ts +getFlatColumns: () => Column[]; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:53](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L53) + +Returns the flattened array of this column and all child/grand-child columns for this column. + +#### Returns + +[`Column`](../type-aliases/Column.md)\<`TFeatures`, `TData`, `TValue`\>[] + +*** + +### getLeafColumns() + +```ts +getLeafColumns: () => Column[]; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:57](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L57) + +Returns an array of all leaf-node columns for this column. If a column has no children, it is considered the only leaf-node column. + +#### Returns + +[`Column`](../type-aliases/Column.md)\<`TFeatures`, `TData`, `TValue`\>[] + +*** + +### id + +```ts +id: string; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:34](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L34) + +The resolved unique identifier for the column resolved in this priority: + - A manual `id` property from the column def + - The accessor key from the column def + - The header string from the column def + +#### Inherited from + +[`Column_CoreProperties`](Column_CoreProperties.md).[`id`](Column_CoreProperties.md#id) + +*** + +### parent? + +```ts +optional parent: Column; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:38](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L38) + +The parent column for this column. Will be undefined if this is a root column. + +#### Inherited from + +[`Column_CoreProperties`](Column_CoreProperties.md).[`parent`](Column_CoreProperties.md#parent) + +*** + +### table + +```ts +table: Table_Internal; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:42](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L42) + +Reference to the parent table instance. + +#### Inherited from + +[`Column_CoreProperties`](Column_CoreProperties.md).[`table`](Column_CoreProperties.md#table) diff --git a/docs/reference/index/interfaces/Column_ColumnFaceting.md b/docs/reference/index/interfaces/Column_ColumnFaceting.md new file mode 100644 index 0000000000..4aa49e99c3 --- /dev/null +++ b/docs/reference/index/interfaces/Column_ColumnFaceting.md @@ -0,0 +1,66 @@ +--- +id: Column_ColumnFaceting +title: Column_ColumnFaceting +--- + +# Interface: Column\_ColumnFaceting\ + +Defined in: [features/column-faceting/columnFacetingFeature.types.ts:6](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.types.ts#L6) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### getFacetedMinMaxValues() + +```ts +getFacetedMinMaxValues: () => [number, number] | undefined; +``` + +Defined in: [features/column-faceting/columnFacetingFeature.types.ts:13](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.types.ts#L13) + +A function that **computes and returns** a min/max tuple derived from `column.getFacetedRowModel`. Useful for displaying faceted result values. + +#### Returns + +\[`number`, `number`\] \| `undefined` + +*** + +### getFacetedRowModel() + +```ts +getFacetedRowModel: () => RowModel; +``` + +Defined in: [features/column-faceting/columnFacetingFeature.types.ts:17](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.types.ts#L17) + +A function that **computes and returns** a row model with all other column filters applied, excluding its own filter. Useful for displaying faceted result counts. + +#### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> + +*** + +### getFacetedUniqueValues() + +```ts +getFacetedUniqueValues: () => Map; +``` + +Defined in: [features/column-faceting/columnFacetingFeature.types.ts:21](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.types.ts#L21) + +Returns a `Map` of unique values and their occurrences derived from `column.getFacetedRowModel`. Useful for displaying faceted result values. + +#### Returns + +`Map`\<`any`, `number`\> diff --git a/docs/reference/index/interfaces/Column_ColumnFiltering.md b/docs/reference/index/interfaces/Column_ColumnFiltering.md new file mode 100644 index 0000000000..6370e2c50c --- /dev/null +++ b/docs/reference/index/interfaces/Column_ColumnFiltering.md @@ -0,0 +1,136 @@ +--- +id: Column_ColumnFiltering +title: Column_ColumnFiltering +--- + +# Interface: Column\_ColumnFiltering\ + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:95](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L95) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### getAutoFilterFn() + +```ts +getAutoFilterFn: () => FilterFn; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:102](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L102) + +Returns an automatically calculated filter function for the column based off of the columns first known value. + +#### Returns + +[`FilterFn`](FilterFn.md)\<`TFeatures`, `TData`\> + +*** + +### getCanFilter() + +```ts +getCanFilter: () => boolean; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:106](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L106) + +Returns whether or not the column can be **column** filtered. + +#### Returns + +`boolean` + +*** + +### getFilterFn() + +```ts +getFilterFn: () => FilterFn; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:110](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L110) + +Returns the filter function (either user-defined or automatic, depending on configuration) for the columnId specified. + +#### Returns + +[`FilterFn`](FilterFn.md)\<`TFeatures`, `TData`\> + +*** + +### getFilterIndex() + +```ts +getFilterIndex: () => number; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:114](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L114) + +Returns the index (including `-1`) of the column filter in the table's `state.columnFilters` array. + +#### Returns + +`number` + +*** + +### getFilterValue() + +```ts +getFilterValue: () => unknown; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:118](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L118) + +Returns the current filter value for the column. + +#### Returns + +`unknown` + +*** + +### getIsFiltered() + +```ts +getIsFiltered: () => boolean; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:122](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L122) + +Returns whether or not the column is currently filtered. + +#### Returns + +`boolean` + +*** + +### setFilterValue() + +```ts +setFilterValue: (updater) => void; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:126](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L126) + +A function that sets the current filter value for the column. You can pass it a value or an updater function for immutability-safe operations on existing values. + +#### Parameters + +##### updater + +`any` + +#### Returns + +`void` diff --git a/docs/reference/index/interfaces/Column_ColumnGrouping.md b/docs/reference/index/interfaces/Column_ColumnGrouping.md new file mode 100644 index 0000000000..e7aef8f88b --- /dev/null +++ b/docs/reference/index/interfaces/Column_ColumnGrouping.md @@ -0,0 +1,142 @@ +--- +id: Column_ColumnGrouping +title: Column_ColumnGrouping +--- + +# Interface: Column\_ColumnGrouping\ + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:78](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L78) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### getAggregationFn() + +```ts +getAggregationFn: () => + | AggregationFn + | undefined; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:85](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L85) + +Returns the aggregation function for the column. + +#### Returns + + \| [`AggregationFn`](../type-aliases/AggregationFn.md)\<`TFeatures`, `TData`\> + \| `undefined` + +*** + +### getAutoAggregationFn() + +```ts +getAutoAggregationFn: () => + | AggregationFn + | undefined; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:89](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L89) + +Returns the automatically inferred aggregation function for the column. + +#### Returns + + \| [`AggregationFn`](../type-aliases/AggregationFn.md)\<`TFeatures`, `TData`\> + \| `undefined` + +*** + +### getCanGroup() + +```ts +getCanGroup: () => boolean; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:93](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L93) + +Returns whether or not the column can be grouped. + +#### Returns + +`boolean` + +*** + +### getGroupedIndex() + +```ts +getGroupedIndex: () => number; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:97](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L97) + +Returns the index of the column in the grouping state. + +#### Returns + +`number` + +*** + +### getIsGrouped() + +```ts +getIsGrouped: () => boolean; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:101](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L101) + +Returns whether or not the column is currently grouped. + +#### Returns + +`boolean` + +*** + +### getToggleGroupingHandler() + +```ts +getToggleGroupingHandler: () => () => void; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:105](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L105) + +Returns a function that toggles the grouping state of the column. This is useful for passing to the `onClick` prop of a button. + +#### Returns + +```ts +(): void; +``` + +##### Returns + +`void` + +*** + +### toggleGrouping() + +```ts +toggleGrouping: () => void; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:109](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L109) + +Toggles the grouping state of the column. + +#### Returns + +`void` diff --git a/docs/reference/index/interfaces/Column_ColumnOrdering.md b/docs/reference/index/interfaces/Column_ColumnOrdering.md new file mode 100644 index 0000000000..ee813495a2 --- /dev/null +++ b/docs/reference/index/interfaces/Column_ColumnOrdering.md @@ -0,0 +1,74 @@ +--- +id: Column_ColumnOrdering +title: Column_ColumnOrdering +--- + +# Interface: Column\_ColumnOrdering + +Defined in: [features/column-ordering/columnOrderingFeature.types.ts:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-ordering/columnOrderingFeature.types.ts#L20) + +## Properties + +### getIndex() + +```ts +getIndex: (position?) => number; +``` + +Defined in: [features/column-ordering/columnOrderingFeature.types.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-ordering/columnOrderingFeature.types.ts#L24) + +Returns the index of the column in the order of the visible columns. Optionally pass a `position` parameter to get the index of the column in a sub-section of the table + +#### Parameters + +##### position? + +[`ColumnPinningPosition`](../type-aliases/ColumnPinningPosition.md) | `"center"` + +#### Returns + +`number` + +*** + +### getIsFirstColumn() + +```ts +getIsFirstColumn: (position?) => boolean; +``` + +Defined in: [features/column-ordering/columnOrderingFeature.types.ts:28](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-ordering/columnOrderingFeature.types.ts#L28) + +Returns `true` if the column is the first column in the order of the visible columns. Optionally pass a `position` parameter to check if the column is the first in a sub-section of the table. + +#### Parameters + +##### position? + +[`ColumnPinningPosition`](../type-aliases/ColumnPinningPosition.md) | `"center"` + +#### Returns + +`boolean` + +*** + +### getIsLastColumn() + +```ts +getIsLastColumn: (position?) => boolean; +``` + +Defined in: [features/column-ordering/columnOrderingFeature.types.ts:32](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-ordering/columnOrderingFeature.types.ts#L32) + +Returns `true` if the column is the last column in the order of the visible columns. Optionally pass a `position` parameter to check if the column is the last in a sub-section of the table. + +#### Parameters + +##### position? + +[`ColumnPinningPosition`](../type-aliases/ColumnPinningPosition.md) | `"center"` + +#### Returns + +`boolean` diff --git a/docs/reference/index/interfaces/Column_ColumnPinning.md b/docs/reference/index/interfaces/Column_ColumnPinning.md new file mode 100644 index 0000000000..d9d361eaab --- /dev/null +++ b/docs/reference/index/interfaces/Column_ColumnPinning.md @@ -0,0 +1,78 @@ +--- +id: Column_ColumnPinning +title: Column_ColumnPinning +--- + +# Interface: Column\_ColumnPinning + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:43](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L43) + +## Properties + +### getCanPin() + +```ts +getCanPin: () => boolean; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:47](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L47) + +Returns whether or not the column can be pinned. + +#### Returns + +`boolean` + +*** + +### getIsPinned() + +```ts +getIsPinned: () => ColumnPinningPosition; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:51](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L51) + +Returns the pinned position of the column. (`'left'`, `'right'` or `false`) + +#### Returns + +[`ColumnPinningPosition`](../type-aliases/ColumnPinningPosition.md) + +*** + +### getPinnedIndex() + +```ts +getPinnedIndex: () => number; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:55](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L55) + +Returns the numeric pinned index of the column within a pinned column group. + +#### Returns + +`number` + +*** + +### pin() + +```ts +pin: (position) => void; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:59](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L59) + +Pins a column to the `'left'` or `'right'`, or unpins the column to the center if `false` is passed. + +#### Parameters + +##### position + +[`ColumnPinningPosition`](../type-aliases/ColumnPinningPosition.md) + +#### Returns + +`void` diff --git a/docs/reference/index/interfaces/Column_ColumnResizing.md b/docs/reference/index/interfaces/Column_ColumnResizing.md new file mode 100644 index 0000000000..be0c59f8eb --- /dev/null +++ b/docs/reference/index/interfaces/Column_ColumnResizing.md @@ -0,0 +1,40 @@ +--- +id: Column_ColumnResizing +title: Column_ColumnResizing +--- + +# Interface: Column\_ColumnResizing + +Defined in: [features/column-resizing/columnResizingFeature.types.ts:70](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts#L70) + +## Properties + +### getCanResize() + +```ts +getCanResize: () => boolean; +``` + +Defined in: [features/column-resizing/columnResizingFeature.types.ts:74](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts#L74) + +Returns `true` if the column can be resized. + +#### Returns + +`boolean` + +*** + +### getIsResizing() + +```ts +getIsResizing: () => boolean; +``` + +Defined in: [features/column-resizing/columnResizingFeature.types.ts:78](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts#L78) + +Returns `true` if the column is currently being resized. + +#### Returns + +`boolean` diff --git a/docs/reference/index/interfaces/Column_ColumnSizing.md b/docs/reference/index/interfaces/Column_ColumnSizing.md new file mode 100644 index 0000000000..6bdbaf1f0a --- /dev/null +++ b/docs/reference/index/interfaces/Column_ColumnSizing.md @@ -0,0 +1,84 @@ +--- +id: Column_ColumnSizing +title: Column_ColumnSizing +--- + +# Interface: Column\_ColumnSizing + +Defined in: [features/column-sizing/columnSizingFeature.types.ts:67](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.types.ts#L67) + +## Properties + +### getAfter() + +```ts +getAfter: (position?) => number; +``` + +Defined in: [features/column-sizing/columnSizingFeature.types.ts:71](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.types.ts#L71) + +Returns the offset measurement along the row-axis (usually the x-axis for standard tables) for the header. This is effectively a sum of the offset measurements of all succeeding (right) headers in relation to the current column. + +#### Parameters + +##### position? + +[`ColumnPinningPosition`](../type-aliases/ColumnPinningPosition.md) | `"center"` + +#### Returns + +`number` + +*** + +### getSize() + +```ts +getSize: () => number; +``` + +Defined in: [features/column-sizing/columnSizingFeature.types.ts:75](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.types.ts#L75) + +Returns the current size of the column. + +#### Returns + +`number` + +*** + +### getStart() + +```ts +getStart: (position?) => number; +``` + +Defined in: [features/column-sizing/columnSizingFeature.types.ts:79](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.types.ts#L79) + +Returns the offset measurement along the row-axis (usually the x-axis for standard tables) for the header. This is effectively a sum of the offset measurements of all preceding (left) headers in relation to the current column. + +#### Parameters + +##### position? + +[`ColumnPinningPosition`](../type-aliases/ColumnPinningPosition.md) | `"center"` + +#### Returns + +`number` + +*** + +### resetSize() + +```ts +resetSize: () => void; +``` + +Defined in: [features/column-sizing/columnSizingFeature.types.ts:83](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.types.ts#L83) + +Resets the column to its initial size. + +#### Returns + +`void` diff --git a/docs/reference/index/interfaces/Column_ColumnVisibility.md b/docs/reference/index/interfaces/Column_ColumnVisibility.md new file mode 100644 index 0000000000..9b139d0a43 --- /dev/null +++ b/docs/reference/index/interfaces/Column_ColumnVisibility.md @@ -0,0 +1,90 @@ +--- +id: Column_ColumnVisibility +title: Column_ColumnVisibility +--- + +# Interface: Column\_ColumnVisibility + +Defined in: [features/column-visibility/columnVisibilityFeature.types.ts:89](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.types.ts#L89) + +## Properties + +### getCanHide() + +```ts +getCanHide: () => boolean; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.types.ts:93](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.types.ts#L93) + +Returns whether the column can be hidden + +#### Returns + +`boolean` + +*** + +### getIsVisible() + +```ts +getIsVisible: () => boolean; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.types.ts:97](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.types.ts#L97) + +Returns whether the column is visible + +#### Returns + +`boolean` + +*** + +### getToggleVisibilityHandler() + +```ts +getToggleVisibilityHandler: () => (event) => void; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.types.ts:101](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.types.ts#L101) + +Returns a function that can be used to toggle the column visibility. This function can be used to bind to an event handler to a checkbox. + +#### Returns + +```ts +(event): void; +``` + +##### Parameters + +###### event + +`unknown` + +##### Returns + +`void` + +*** + +### toggleVisibility() + +```ts +toggleVisibility: (value?) => void; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.types.ts:105](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.types.ts#L105) + +Toggles the visibility of the column. + +#### Parameters + +##### value? + +`boolean` + +#### Returns + +`void` diff --git a/docs/reference/index/interfaces/Column_Core.md b/docs/reference/index/interfaces/Column_Core.md new file mode 100644 index 0000000000..19778f5f47 --- /dev/null +++ b/docs/reference/index/interfaces/Column_Core.md @@ -0,0 +1,181 @@ +--- +id: Column_Core +title: Column_Core +--- + +# Interface: Column\_Core\ + +Defined in: [types/Column.ts:26](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L26) + +## Extends + +- [`Column_Column`](Column_Column.md)\<`TFeatures`, `TData`, `TValue`\> + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` = `unknown` + +## Properties + +### accessorFn? + +```ts +optional accessorFn: AccessorFn; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:15](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L15) + +The resolved accessor function to use when extracting the value for the column from each row. Will only be defined if the column def has a valid accessor key or function defined. + +#### Inherited from + +[`Column_Column`](Column_Column.md).[`accessorFn`](Column_Column.md#accessorfn) + +*** + +### columnDef + +```ts +columnDef: ColumnDef; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:19](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L19) + +The original column def used to create the column. + +#### Inherited from + +[`Column_Column`](Column_Column.md).[`columnDef`](Column_Column.md#columndef) + +*** + +### columns + +```ts +columns: Column[]; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:23](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L23) + +The child column (if the column is a group column). Will be an empty array if the column is not a group column. + +#### Inherited from + +[`Column_Column`](Column_Column.md).[`columns`](Column_Column.md#columns) + +*** + +### depth + +```ts +depth: number; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:27](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L27) + +The depth of the column (if grouped) relative to the root column def array. + +#### Inherited from + +[`Column_Column`](Column_Column.md).[`depth`](Column_Column.md#depth) + +*** + +### getFlatColumns() + +```ts +getFlatColumns: () => Column[]; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:53](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L53) + +Returns the flattened array of this column and all child/grand-child columns for this column. + +#### Returns + +[`Column`](../type-aliases/Column.md)\<`TFeatures`, `TData`, `TValue`\>[] + +#### Inherited from + +[`Column_Column`](Column_Column.md).[`getFlatColumns`](Column_Column.md#getflatcolumns) + +*** + +### getLeafColumns() + +```ts +getLeafColumns: () => Column[]; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:57](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L57) + +Returns an array of all leaf-node columns for this column. If a column has no children, it is considered the only leaf-node column. + +#### Returns + +[`Column`](../type-aliases/Column.md)\<`TFeatures`, `TData`, `TValue`\>[] + +#### Inherited from + +[`Column_Column`](Column_Column.md).[`getLeafColumns`](Column_Column.md#getleafcolumns) + +*** + +### id + +```ts +id: string; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:34](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L34) + +The resolved unique identifier for the column resolved in this priority: + - A manual `id` property from the column def + - The accessor key from the column def + - The header string from the column def + +#### Inherited from + +[`Column_Column`](Column_Column.md).[`id`](Column_Column.md#id) + +*** + +### parent? + +```ts +optional parent: Column; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:38](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L38) + +The parent column for this column. Will be undefined if this is a root column. + +#### Inherited from + +[`Column_Column`](Column_Column.md).[`parent`](Column_Column.md#parent) + +*** + +### table + +```ts +table: Table_Internal; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:42](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L42) + +Reference to the parent table instance. + +#### Inherited from + +[`Column_Column`](Column_Column.md).[`table`](Column_Column.md#table) diff --git a/docs/reference/index/interfaces/Column_CoreProperties.md b/docs/reference/index/interfaces/Column_CoreProperties.md new file mode 100644 index 0000000000..7fbb00c16d --- /dev/null +++ b/docs/reference/index/interfaces/Column_CoreProperties.md @@ -0,0 +1,113 @@ +--- +id: Column_CoreProperties +title: Column_CoreProperties +--- + +# Interface: Column\_CoreProperties\ + +Defined in: [core/columns/coreColumnsFeature.types.ts:7](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L7) + +## Extended by + +- [`Column_Column`](Column_Column.md) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](../type-aliases/CellData.md) = [`CellData`](../type-aliases/CellData.md) + +## Properties + +### accessorFn? + +```ts +optional accessorFn: AccessorFn; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:15](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L15) + +The resolved accessor function to use when extracting the value for the column from each row. Will only be defined if the column def has a valid accessor key or function defined. + +*** + +### columnDef + +```ts +columnDef: ColumnDef; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:19](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L19) + +The original column def used to create the column. + +*** + +### columns + +```ts +columns: Column[]; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:23](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L23) + +The child column (if the column is a group column). Will be an empty array if the column is not a group column. + +*** + +### depth + +```ts +depth: number; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:27](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L27) + +The depth of the column (if grouped) relative to the root column def array. + +*** + +### id + +```ts +id: string; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:34](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L34) + +The resolved unique identifier for the column resolved in this priority: + - A manual `id` property from the column def + - The accessor key from the column def + - The header string from the column def + +*** + +### parent? + +```ts +optional parent: Column; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:38](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L38) + +The parent column for this column. Will be undefined if this is a root column. + +*** + +### table + +```ts +table: Table_Internal; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:42](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L42) + +Reference to the parent table instance. diff --git a/docs/reference/index/interfaces/Column_GlobalFiltering.md b/docs/reference/index/interfaces/Column_GlobalFiltering.md new file mode 100644 index 0000000000..2f0e9d98b7 --- /dev/null +++ b/docs/reference/index/interfaces/Column_GlobalFiltering.md @@ -0,0 +1,24 @@ +--- +id: Column_GlobalFiltering +title: Column_GlobalFiltering +--- + +# Interface: Column\_GlobalFiltering + +Defined in: [features/global-filtering/globalFilteringFeature.types.ts:25](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.types.ts#L25) + +## Properties + +### getCanGlobalFilter() + +```ts +getCanGlobalFilter: () => boolean; +``` + +Defined in: [features/global-filtering/globalFilteringFeature.types.ts:29](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.types.ts#L29) + +Returns whether or not the column can be **globally** filtered. Set to `false` to disable a column from being scanned during global filtering. + +#### Returns + +`boolean` diff --git a/docs/reference/index/interfaces/Column_Plugins.md b/docs/reference/index/interfaces/Column_Plugins.md new file mode 100644 index 0000000000..1034f8f551 --- /dev/null +++ b/docs/reference/index/interfaces/Column_Plugins.md @@ -0,0 +1,25 @@ +--- +id: Column_Plugins +title: Column_Plugins +--- + +# Interface: Column\_Plugins\ + +Defined in: [types/Column.ts:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L20) + +Use this interface as a target for declaration merging to add your own plugin properties. +Note: This will affect the types of all tables in your project. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` = `unknown` diff --git a/docs/reference/index/interfaces/Column_RowSorting.md b/docs/reference/index/interfaces/Column_RowSorting.md new file mode 100644 index 0000000000..52460eb9af --- /dev/null +++ b/docs/reference/index/interfaces/Column_RowSorting.md @@ -0,0 +1,220 @@ +--- +id: Column_RowSorting +title: Column_RowSorting +--- + +# Interface: Column\_RowSorting\ + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:89](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L89) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### clearSorting() + +```ts +clearSorting: () => void; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:96](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L96) + +Removes this column from the table's sorting state + +#### Returns + +`void` + +*** + +### getAutoSortDir() + +```ts +getAutoSortDir: () => SortDirection; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:100](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L100) + +Returns a sort direction automatically inferred based on the columns values. + +#### Returns + +[`SortDirection`](../type-aliases/SortDirection.md) + +*** + +### getAutoSortFn() + +```ts +getAutoSortFn: () => SortFn; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:104](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L104) + +Returns a sorting function automatically inferred based on the columns values. + +#### Returns + +[`SortFn`](SortFn.md)\<`TFeatures`, `TData`\> + +*** + +### getCanMultiSort() + +```ts +getCanMultiSort: () => boolean; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:108](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L108) + +Returns whether this column can be multi-sorted. + +#### Returns + +`boolean` + +*** + +### getCanSort() + +```ts +getCanSort: () => boolean; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:112](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L112) + +Returns whether this column can be sorted. + +#### Returns + +`boolean` + +*** + +### getFirstSortDir() + +```ts +getFirstSortDir: () => SortDirection; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:116](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L116) + +Returns the first direction that should be used when sorting this column. + +#### Returns + +[`SortDirection`](../type-aliases/SortDirection.md) + +*** + +### getIsSorted() + +```ts +getIsSorted: () => false | SortDirection; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:120](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L120) + +Returns the current sort direction of this column. + +#### Returns + +`false` \| [`SortDirection`](../type-aliases/SortDirection.md) + +*** + +### getNextSortingOrder() + +```ts +getNextSortingOrder: () => false | SortDirection; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:124](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L124) + +Returns the next sorting order. + +#### Returns + +`false` \| [`SortDirection`](../type-aliases/SortDirection.md) + +*** + +### getSortFn() + +```ts +getSortFn: () => SortFn; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:132](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L132) + +Returns the resolved sorting function to be used for this column + +#### Returns + +[`SortFn`](SortFn.md)\<`TFeatures`, `TData`\> + +*** + +### getSortIndex() + +```ts +getSortIndex: () => number; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:128](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L128) + +Returns the index position of this column's sorting within the sorting state + +#### Returns + +`number` + +*** + +### getToggleSortingHandler() + +```ts +getToggleSortingHandler: () => (event) => void | undefined; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:136](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L136) + +Returns a function that can be used to toggle this column's sorting state. This is useful for attaching a click handler to the column header. + +#### Returns + +(`event`) => `void` \| `undefined` + +*** + +### toggleSorting() + +```ts +toggleSorting: (desc?, isMulti?) => void; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:140](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L140) + +Toggles this columns sorting state. If `desc` is provided, it will force the sort direction to that value. If `isMulti` is provided, it will additivity multi-sort the column (or toggle it if it is already sorted). + +#### Parameters + +##### desc? + +`boolean` + +##### isMulti? + +`boolean` + +#### Returns + +`void` diff --git a/docs/reference/index/interfaces/CoreCellsFeatureConstructors.md b/docs/reference/index/interfaces/CoreCellsFeatureConstructors.md new file mode 100644 index 0000000000..05ed3f6d91 --- /dev/null +++ b/docs/reference/index/interfaces/CoreCellsFeatureConstructors.md @@ -0,0 +1,18 @@ +--- +id: CoreCellsFeatureConstructors +title: CoreCellsFeatureConstructors +--- + +# Interface: CoreCellsFeatureConstructors\ + +Defined in: [core/cells/coreCellsFeature.ts:11](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.ts#L11) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/CoreColumnsFeatureConstructors.md b/docs/reference/index/interfaces/CoreColumnsFeatureConstructors.md new file mode 100644 index 0000000000..1d14dca4e5 --- /dev/null +++ b/docs/reference/index/interfaces/CoreColumnsFeatureConstructors.md @@ -0,0 +1,18 @@ +--- +id: CoreColumnsFeatureConstructors +title: CoreColumnsFeatureConstructors +--- + +# Interface: CoreColumnsFeatureConstructors\ + +Defined in: [core/columns/coreColumnsFeature.ts:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.ts#L20) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/CoreFeatures.md b/docs/reference/index/interfaces/CoreFeatures.md new file mode 100644 index 0000000000..d3bde6f41f --- /dev/null +++ b/docs/reference/index/interfaces/CoreFeatures.md @@ -0,0 +1,78 @@ +--- +id: CoreFeatures +title: CoreFeatures +--- + +# Interface: CoreFeatures + +Defined in: [core/coreFeatures.ts:9](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/coreFeatures.ts#L9) + +## Properties + +### coreCellsFeature + +```ts +coreCellsFeature: TableFeature>; +``` + +Defined in: [core/coreFeatures.ts:11](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/coreFeatures.ts#L11) + +*** + +### coreColumnsFeature + +```ts +coreColumnsFeature: TableFeature>; +``` + +Defined in: [core/coreFeatures.ts:12](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/coreFeatures.ts#L12) + +*** + +### coreHeadersFeature + +```ts +coreHeadersFeature: TableFeature>; +``` + +Defined in: [core/coreFeatures.ts:13](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/coreFeatures.ts#L13) + +*** + +### coreReativityFeature? + +```ts +optional coreReativityFeature: TableReactivityBindings; +``` + +Defined in: [core/coreFeatures.ts:10](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/coreFeatures.ts#L10) + +*** + +### coreRowModelsFeature + +```ts +coreRowModelsFeature: TableFeature>; +``` + +Defined in: [core/coreFeatures.ts:14](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/coreFeatures.ts#L14) + +*** + +### coreRowsFeature + +```ts +coreRowsFeature: TableFeature>; +``` + +Defined in: [core/coreFeatures.ts:15](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/coreFeatures.ts#L15) + +*** + +### coreTablesFeature + +```ts +coreTablesFeature: TableFeature>; +``` + +Defined in: [core/coreFeatures.ts:16](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/coreFeatures.ts#L16) diff --git a/docs/reference/index/interfaces/CoreHeadersFeatureConstructors.md b/docs/reference/index/interfaces/CoreHeadersFeatureConstructors.md new file mode 100644 index 0000000000..8ae4912b7a --- /dev/null +++ b/docs/reference/index/interfaces/CoreHeadersFeatureConstructors.md @@ -0,0 +1,18 @@ +--- +id: CoreHeadersFeatureConstructors +title: CoreHeadersFeatureConstructors +--- + +# Interface: CoreHeadersFeatureConstructors\ + +Defined in: [core/headers/coreHeadersFeature.ts:23](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.ts#L23) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/CoreRowModelsFeatureConstructors.md b/docs/reference/index/interfaces/CoreRowModelsFeatureConstructors.md new file mode 100644 index 0000000000..b0b670a2b2 --- /dev/null +++ b/docs/reference/index/interfaces/CoreRowModelsFeatureConstructors.md @@ -0,0 +1,18 @@ +--- +id: CoreRowModelsFeatureConstructors +title: CoreRowModelsFeatureConstructors +--- + +# Interface: CoreRowModelsFeatureConstructors\ + +Defined in: [core/row-models/coreRowModelsFeature.ts:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.ts#L20) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/CoreRowsFeatureConstructors.md b/docs/reference/index/interfaces/CoreRowsFeatureConstructors.md new file mode 100644 index 0000000000..b12db0848e --- /dev/null +++ b/docs/reference/index/interfaces/CoreRowsFeatureConstructors.md @@ -0,0 +1,18 @@ +--- +id: CoreRowsFeatureConstructors +title: CoreRowsFeatureConstructors +--- + +# Interface: CoreRowsFeatureConstructors\ + +Defined in: [core/rows/coreRowsFeature.ts:22](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.ts#L22) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/CoreTablesFeatureConstructors.md b/docs/reference/index/interfaces/CoreTablesFeatureConstructors.md new file mode 100644 index 0000000000..d17cf0b2f1 --- /dev/null +++ b/docs/reference/index/interfaces/CoreTablesFeatureConstructors.md @@ -0,0 +1,18 @@ +--- +id: CoreTablesFeatureConstructors +title: CoreTablesFeatureConstructors +--- + +# Interface: CoreTablesFeatureConstructors\ + +Defined in: [core/table/coreTablesFeature.ts:7](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.ts#L7) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/CreateRowModel_Core.md b/docs/reference/index/interfaces/CreateRowModel_Core.md new file mode 100644 index 0000000000..448cc396bc --- /dev/null +++ b/docs/reference/index/interfaces/CreateRowModel_Core.md @@ -0,0 +1,51 @@ +--- +id: CreateRowModel_Core +title: CreateRowModel_Core +--- + +# Interface: CreateRowModel\_Core\ + +Defined in: [core/row-models/coreRowModelsFeature.types.ts:23](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.types.ts#L23) + +## Extends + +- [`CreateRowModel_Plugins`](CreateRowModel_Plugins.md) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### coreRowModel()? + +```ts +optional coreRowModel: (table) => () => RowModel; +``` + +Defined in: [core/row-models/coreRowModelsFeature.types.ts:31](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.types.ts#L31) + +Optional factory for the core row model. When omitted, the built-in +`createCoreRowModel()` factory is used. + +#### Parameters + +##### table + +[`Table`](../type-aliases/Table.md)\<`TFeatures`, `TData`\> + +#### Returns + +```ts +(): RowModel; +``` + +##### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/interfaces/CreateRowModel_Expanded.md b/docs/reference/index/interfaces/CreateRowModel_Expanded.md new file mode 100644 index 0000000000..9459d0a031 --- /dev/null +++ b/docs/reference/index/interfaces/CreateRowModel_Expanded.md @@ -0,0 +1,48 @@ +--- +id: CreateRowModel_Expanded +title: CreateRowModel_Expanded +--- + +# Interface: CreateRowModel\_Expanded\ + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:127](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L127) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### expandedRowModel()? + +```ts +optional expandedRowModel: (table) => () => RowModel; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:136](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L136) + +Factory used to retrieve the expanded row model. If this function is not +provided, the table will not expand rows. To use client-side expansion, +pass `createExpandedRowModel()` or implement your own factory. + +#### Parameters + +##### table + +[`Table`](../type-aliases/Table.md)\<`TFeatures`, `TData`\> + +#### Returns + +```ts +(): RowModel; +``` + +##### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/interfaces/CreateRowModel_Faceted.md b/docs/reference/index/interfaces/CreateRowModel_Faceted.md new file mode 100644 index 0000000000..0720f2d497 --- /dev/null +++ b/docs/reference/index/interfaces/CreateRowModel_Faceted.md @@ -0,0 +1,120 @@ +--- +id: CreateRowModel_Faceted +title: CreateRowModel_Faceted +--- + +# Interface: CreateRowModel\_Faceted\ + +Defined in: [features/column-faceting/columnFacetingFeature.types.ts:45](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.types.ts#L45) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### facetedMinMaxValues()? + +```ts +optional facetedMinMaxValues: (table, columnId) => () => [number, number] | undefined; +``` + +Defined in: [features/column-faceting/columnFacetingFeature.types.ts:54](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.types.ts#L54) + +Factory used to retrieve faceted min/max values. If using server-side +faceting, this is not required. To use client-side faceting, pass +`createFacetedMinMaxValues()` or implement your own factory. + +#### Parameters + +##### table + +[`Table`](../type-aliases/Table.md)\<`TFeatures`, `TData`\> + +##### columnId + +`string` + +#### Returns + +```ts +(): [number, number] | undefined; +``` + +##### Returns + +\[`number`, `number`\] \| `undefined` + +*** + +### facetedRowModel()? + +```ts +optional facetedRowModel: (table, columnId) => () => RowModel; +``` + +Defined in: [features/column-faceting/columnFacetingFeature.types.ts:63](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.types.ts#L63) + +Factory used to retrieve the faceted row model. If using server-side +faceting, this is not required. To use client-side faceting, pass +`createFacetedRowModel()` or implement your own factory. + +#### Parameters + +##### table + +[`Table`](../type-aliases/Table.md)\<`TFeatures`, `TData`\> + +##### columnId + +`string` + +#### Returns + +```ts +(): RowModel; +``` + +##### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> + +*** + +### facetedUniqueValues()? + +```ts +optional facetedUniqueValues: (table, columnId) => () => Map; +``` + +Defined in: [features/column-faceting/columnFacetingFeature.types.ts:72](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.types.ts#L72) + +Factory used to retrieve faceted unique values. If using server-side +faceting, this is not required. To use client-side faceting, pass +`createFacetedUniqueValues()` or implement your own factory. + +#### Parameters + +##### table + +[`Table`](../type-aliases/Table.md)\<`TFeatures`, `TData`\> + +##### columnId + +`string` + +#### Returns + +```ts +(): Map; +``` + +##### Returns + +`Map`\<`any`, `number`\> diff --git a/docs/reference/index/interfaces/CreateRowModel_Filtered.md b/docs/reference/index/interfaces/CreateRowModel_Filtered.md new file mode 100644 index 0000000000..5e3b9d0d66 --- /dev/null +++ b/docs/reference/index/interfaces/CreateRowModel_Filtered.md @@ -0,0 +1,49 @@ +--- +id: CreateRowModel_Filtered +title: CreateRowModel_Filtered +--- + +# Interface: CreateRowModel\_Filtered\ + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:202](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L202) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### filteredRowModel()? + +```ts +optional filteredRowModel: (table) => () => RowModel; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:212](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L212) + +If provided, this factory is called once per table and should return a +function that calculates the filtered row model. +- For server-side filtering, this function is unnecessary and can be ignored since the server should already return the filtered row model. +- For client-side filtering, pass the exported `createFilteredRowModel()` or implement your own factory. + +#### Parameters + +##### table + +[`Table`](../type-aliases/Table.md)\<`TFeatures`, `TData`\> + +#### Returns + +```ts +(): RowModel; +``` + +##### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/interfaces/CreateRowModel_Grouped.md b/docs/reference/index/interfaces/CreateRowModel_Grouped.md new file mode 100644 index 0000000000..3ca42a191d --- /dev/null +++ b/docs/reference/index/interfaces/CreateRowModel_Grouped.md @@ -0,0 +1,48 @@ +--- +id: CreateRowModel_Grouped +title: CreateRowModel_Grouped +--- + +# Interface: CreateRowModel\_Grouped\ + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:203](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L203) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### groupedRowModel()? + +```ts +optional groupedRowModel: (table) => () => RowModel; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:212](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L212) + +Factory used to retrieve the grouped row model. If using server-side +grouping, this is not required. To use client-side grouping, pass +`createGroupedRowModel()` or implement your own factory. + +#### Parameters + +##### table + +[`Table`](../type-aliases/Table.md)\<`TFeatures`, `TData`\> + +#### Returns + +```ts +(): RowModel; +``` + +##### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/interfaces/CreateRowModel_Paginated.md b/docs/reference/index/interfaces/CreateRowModel_Paginated.md new file mode 100644 index 0000000000..3d6046cadb --- /dev/null +++ b/docs/reference/index/interfaces/CreateRowModel_Paginated.md @@ -0,0 +1,48 @@ +--- +id: CreateRowModel_Paginated +title: CreateRowModel_Paginated +--- + +# Interface: CreateRowModel\_Paginated\ + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:126](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L126) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### paginatedRowModel()? + +```ts +optional paginatedRowModel: (table) => () => RowModel; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:135](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L135) + +Factory used to retrieve the paginated row model. If using server-side +pagination, this is not required. To use client-side pagination, pass +`createPaginatedRowModel()` or implement your own factory. + +#### Parameters + +##### table + +[`Table`](../type-aliases/Table.md)\<`TFeatures`, `TData`\> + +#### Returns + +```ts +(): RowModel; +``` + +##### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/interfaces/CreateRowModel_Plugins.md b/docs/reference/index/interfaces/CreateRowModel_Plugins.md new file mode 100644 index 0000000000..d548153f1b --- /dev/null +++ b/docs/reference/index/interfaces/CreateRowModel_Plugins.md @@ -0,0 +1,12 @@ +--- +id: CreateRowModel_Plugins +title: CreateRowModel_Plugins +--- + +# Interface: CreateRowModel\_Plugins + +Defined in: [core/row-models/coreRowModelsFeature.types.ts:21](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.types.ts#L21) + +## Extended by + +- [`CreateRowModel_Core`](CreateRowModel_Core.md) diff --git a/docs/reference/index/interfaces/CreateRowModel_Sorted.md b/docs/reference/index/interfaces/CreateRowModel_Sorted.md new file mode 100644 index 0000000000..75b1439e5b --- /dev/null +++ b/docs/reference/index/interfaces/CreateRowModel_Sorted.md @@ -0,0 +1,48 @@ +--- +id: CreateRowModel_Sorted +title: CreateRowModel_Sorted +--- + +# Interface: CreateRowModel\_Sorted\ + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:214](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L214) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### sortedRowModel()? + +```ts +optional sortedRowModel: (table) => () => RowModel; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:223](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L223) + +Factory used to retrieve the sorted row model. If using server-side +sorting, this is not required. To use client-side sorting, pass the +exported `createSortedRowModel()` or implement your own factory. + +#### Parameters + +##### table + +[`Table`](../type-aliases/Table.md)\<`TFeatures`, `TData`\> + +#### Returns + +```ts +(): RowModel; +``` + +##### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/interfaces/CreateRowModels_Plugins.md b/docs/reference/index/interfaces/CreateRowModels_Plugins.md new file mode 100644 index 0000000000..6604e4bdfa --- /dev/null +++ b/docs/reference/index/interfaces/CreateRowModels_Plugins.md @@ -0,0 +1,21 @@ +--- +id: CreateRowModels_Plugins +title: CreateRowModels_Plugins +--- + +# Interface: CreateRowModels\_Plugins\ + +Defined in: [types/RowModel.ts:37](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/RowModel.ts#L37) + +Use this interface as a target for declaration merging to add your own plugin properties. +Note: This will affect the types of all tables in your project. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/FeatureConstructors.md b/docs/reference/index/interfaces/FeatureConstructors.md new file mode 100644 index 0000000000..2e17fc27f6 --- /dev/null +++ b/docs/reference/index/interfaces/FeatureConstructors.md @@ -0,0 +1,128 @@ +--- +id: FeatureConstructors +title: FeatureConstructors +--- + +# Interface: FeatureConstructors + +Defined in: [types/TableFeatures.ts:25](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L25) + +## Properties + +### CachedRowModel? + +```ts +optional CachedRowModel: any; +``` + +Defined in: [types/TableFeatures.ts:26](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L26) + +*** + +### Cell? + +```ts +optional Cell: any; +``` + +Defined in: [types/TableFeatures.ts:27](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L27) + +*** + +### Column? + +```ts +optional Column: any; +``` + +Defined in: [types/TableFeatures.ts:28](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L28) + +*** + +### ColumnDef? + +```ts +optional ColumnDef: any; +``` + +Defined in: [types/TableFeatures.ts:29](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L29) + +*** + +### CreateRowModels? + +```ts +optional CreateRowModels: any; +``` + +Defined in: [types/TableFeatures.ts:30](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L30) + +*** + +### Header? + +```ts +optional Header: any; +``` + +Defined in: [types/TableFeatures.ts:31](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L31) + +*** + +### HeaderGroup? + +```ts +optional HeaderGroup: any; +``` + +Defined in: [types/TableFeatures.ts:32](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L32) + +*** + +### Row? + +```ts +optional Row: any; +``` + +Defined in: [types/TableFeatures.ts:33](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L33) + +*** + +### RowModelFns? + +```ts +optional RowModelFns: any; +``` + +Defined in: [types/TableFeatures.ts:34](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L34) + +*** + +### Table? + +```ts +optional Table: any; +``` + +Defined in: [types/TableFeatures.ts:35](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L35) + +*** + +### TableOptions? + +```ts +optional TableOptions: any; +``` + +Defined in: [types/TableFeatures.ts:36](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L36) + +*** + +### TableState? + +```ts +optional TableState: any; +``` + +Defined in: [types/TableFeatures.ts:37](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L37) diff --git a/docs/reference/index/interfaces/FilterFn.md b/docs/reference/index/interfaces/FilterFn.md new file mode 100644 index 0000000000..3bfeb7a15f --- /dev/null +++ b/docs/reference/index/interfaces/FilterFn.md @@ -0,0 +1,70 @@ +--- +id: FilterFn +title: FilterFn +--- + +# Interface: FilterFn()\ + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:45](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L45) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +```ts +FilterFn( + row, + columnId, + filterValue, + addMeta?): boolean; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:49](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L49) + +## Parameters + +### row + +[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\> + +### columnId + +`string` + +### filterValue + +`any` + +### addMeta? + +(`meta`) => `void` + +## Returns + +`boolean` + +## Properties + +### autoRemove? + +```ts +optional autoRemove: ColumnFilterAutoRemoveTestFn; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:55](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L55) + +*** + +### resolveFilterValue? + +```ts +optional resolveFilterValue: TransformFilterValueFn; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:56](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L56) diff --git a/docs/reference/index/interfaces/FilterFns.md b/docs/reference/index/interfaces/FilterFns.md new file mode 100644 index 0000000000..48e1d51a89 --- /dev/null +++ b/docs/reference/index/interfaces/FilterFns.md @@ -0,0 +1,8 @@ +--- +id: FilterFns +title: FilterFns +--- + +# Interface: FilterFns + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:16](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L16) diff --git a/docs/reference/index/interfaces/FilterMeta.md b/docs/reference/index/interfaces/FilterMeta.md new file mode 100644 index 0000000000..75b2577be3 --- /dev/null +++ b/docs/reference/index/interfaces/FilterMeta.md @@ -0,0 +1,8 @@ +--- +id: FilterMeta +title: FilterMeta +--- + +# Interface: FilterMeta + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:14](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L14) diff --git a/docs/reference/index/interfaces/GlobalFilteringFeatureConstructors.md b/docs/reference/index/interfaces/GlobalFilteringFeatureConstructors.md new file mode 100644 index 0000000000..1b518cdcb7 --- /dev/null +++ b/docs/reference/index/interfaces/GlobalFilteringFeatureConstructors.md @@ -0,0 +1,18 @@ +--- +id: GlobalFilteringFeatureConstructors +title: GlobalFilteringFeatureConstructors +--- + +# Interface: GlobalFilteringFeatureConstructors\ + +Defined in: [features/global-filtering/globalFilteringFeature.ts:23](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.ts#L23) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/HeaderContext.md b/docs/reference/index/interfaces/HeaderContext.md new file mode 100644 index 0000000000..20f0236561 --- /dev/null +++ b/docs/reference/index/interfaces/HeaderContext.md @@ -0,0 +1,58 @@ +--- +id: HeaderContext +title: HeaderContext +--- + +# Interface: HeaderContext\ + +Defined in: [core/headers/coreHeadersFeature.types.ts:30](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L30) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](../type-aliases/CellData.md) = [`CellData`](../type-aliases/CellData.md) + +## Properties + +### column + +```ts +column: Column; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:38](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L38) + +An instance of a column. + +*** + +### header + +```ts +header: Header; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:42](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L42) + +An instance of a header. + +*** + +### table + +```ts +table: Table; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:46](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L46) + +The table instance. diff --git a/docs/reference/index/interfaces/HeaderGroup_Core.md b/docs/reference/index/interfaces/HeaderGroup_Core.md new file mode 100644 index 0000000000..99cf395801 --- /dev/null +++ b/docs/reference/index/interfaces/HeaderGroup_Core.md @@ -0,0 +1,64 @@ +--- +id: HeaderGroup_Core +title: HeaderGroup_Core +--- + +# Interface: HeaderGroup\_Core\ + +Defined in: [types/HeaderGroup.ts:14](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/HeaderGroup.ts#L14) + +## Extends + +- [`HeaderGroup_Header`](HeaderGroup_Header.md)\<`TFeatures`, `TData`\> + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### depth + +```ts +depth: number; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:120](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L120) + +#### Inherited from + +[`HeaderGroup_Header`](HeaderGroup_Header.md).[`depth`](HeaderGroup_Header.md#depth) + +*** + +### headers + +```ts +headers: Header[]; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:121](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L121) + +#### Inherited from + +[`HeaderGroup_Header`](HeaderGroup_Header.md).[`headers`](HeaderGroup_Header.md#headers) + +*** + +### id + +```ts +id: string; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:122](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L122) + +#### Inherited from + +[`HeaderGroup_Header`](HeaderGroup_Header.md).[`id`](HeaderGroup_Header.md#id) diff --git a/docs/reference/index/interfaces/HeaderGroup_Header.md b/docs/reference/index/interfaces/HeaderGroup_Header.md new file mode 100644 index 0000000000..5bd51a846d --- /dev/null +++ b/docs/reference/index/interfaces/HeaderGroup_Header.md @@ -0,0 +1,56 @@ +--- +id: HeaderGroup_Header +title: HeaderGroup_Header +--- + +# Interface: HeaderGroup\_Header\ + +Defined in: [core/headers/coreHeadersFeature.types.ts:115](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L115) + +## Extended by + +- [`HeaderGroup_Core`](HeaderGroup_Core.md) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](../type-aliases/CellData.md) = [`CellData`](../type-aliases/CellData.md) + +## Properties + +### depth + +```ts +depth: number; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:120](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L120) + +*** + +### headers + +```ts +headers: Header[]; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:121](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L121) + +*** + +### id + +```ts +id: string; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:122](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L122) diff --git a/docs/reference/index/interfaces/HeaderGroup_Plugins.md b/docs/reference/index/interfaces/HeaderGroup_Plugins.md new file mode 100644 index 0000000000..0852efdc72 --- /dev/null +++ b/docs/reference/index/interfaces/HeaderGroup_Plugins.md @@ -0,0 +1,21 @@ +--- +id: HeaderGroup_Plugins +title: HeaderGroup_Plugins +--- + +# Interface: HeaderGroup\_Plugins\ + +Defined in: [types/HeaderGroup.ts:9](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/HeaderGroup.ts#L9) + +Use this interface as a target for declaration merging to add your own plugin properties. +Note: This will affect the types of all tables in your project. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/Header_ColumnResizing.md b/docs/reference/index/interfaces/Header_ColumnResizing.md new file mode 100644 index 0000000000..2833f9b667 --- /dev/null +++ b/docs/reference/index/interfaces/Header_ColumnResizing.md @@ -0,0 +1,45 @@ +--- +id: Header_ColumnResizing +title: Header_ColumnResizing +--- + +# Interface: Header\_ColumnResizing + +Defined in: [features/column-resizing/columnResizingFeature.types.ts:81](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts#L81) + +## Properties + +### getResizeHandler() + +```ts +getResizeHandler: (context?) => (event) => void; +``` + +Defined in: [features/column-resizing/columnResizingFeature.types.ts:88](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts#L88) + +Returns an event handler function that can be used to resize the header. It can be used as an: +- `onMouseDown` handler +- `onTouchStart` handler +The dragging and release events are automatically handled for you. + +#### Parameters + +##### context? + +`Document` + +#### Returns + +```ts +(event): void; +``` + +##### Parameters + +###### event + +`unknown` + +##### Returns + +`void` diff --git a/docs/reference/index/interfaces/Header_ColumnSizing.md b/docs/reference/index/interfaces/Header_ColumnSizing.md new file mode 100644 index 0000000000..59bd2e239c --- /dev/null +++ b/docs/reference/index/interfaces/Header_ColumnSizing.md @@ -0,0 +1,46 @@ +--- +id: Header_ColumnSizing +title: Header_ColumnSizing +--- + +# Interface: Header\_ColumnSizing + +Defined in: [features/column-sizing/columnSizingFeature.types.ts:86](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.types.ts#L86) + +## Properties + +### getSize() + +```ts +getSize: () => number; +``` + +Defined in: [features/column-sizing/columnSizingFeature.types.ts:90](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.types.ts#L90) + +Returns the current size of the header. + +#### Returns + +`number` + +*** + +### getStart() + +```ts +getStart: (position?) => number; +``` + +Defined in: [features/column-sizing/columnSizingFeature.types.ts:94](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.types.ts#L94) + +Returns the offset measurement along the row-axis (usually the x-axis for standard tables) for the header. This is effectively a sum of the offset measurements of all preceding headers. + +#### Parameters + +##### position? + +[`ColumnPinningPosition`](../type-aliases/ColumnPinningPosition.md) + +#### Returns + +`number` diff --git a/docs/reference/index/interfaces/Header_Core.md b/docs/reference/index/interfaces/Header_Core.md new file mode 100644 index 0000000000..fd4980f9ba --- /dev/null +++ b/docs/reference/index/interfaces/Header_Core.md @@ -0,0 +1,244 @@ +--- +id: Header_Core +title: Header_Core +--- + +# Interface: Header\_Core\ + +Defined in: [types/Header.ts:17](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Header.ts#L17) + +## Extends + +- [`Header_Header`](Header_Header.md)\<`TFeatures`, `TData`, `TValue`\> + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](../type-aliases/CellData.md) = [`CellData`](../type-aliases/CellData.md) + +## Properties + +### colSpan + +```ts +colSpan: number; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:57](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L57) + +The col-span for the header. + +#### Inherited from + +[`Header_Header`](Header_Header.md).[`colSpan`](Header_Header.md#colspan) + +*** + +### column + +```ts +column: Column; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:61](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L61) + +The header's associated column object. + +#### Inherited from + +[`Header_Header`](Header_Header.md).[`column`](Header_Header.md#column) + +*** + +### depth + +```ts +depth: number; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:65](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L65) + +The depth of the header, zero-indexed based. + +#### Inherited from + +[`Header_Header`](Header_Header.md).[`depth`](Header_Header.md#depth) + +*** + +### getContext() + +```ts +getContext: () => HeaderContext; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:108](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L108) + +Returns the rendering context (or props) for column-based components like headers, footers and filters. + +#### Returns + +[`HeaderContext`](HeaderContext.md)\<`TFeatures`, `TData`, `TValue`\> + +#### Inherited from + +[`Header_Header`](Header_Header.md).[`getContext`](Header_Header.md#getcontext) + +*** + +### getLeafHeaders() + +```ts +getLeafHeaders: () => Header[]; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:112](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L112) + +Returns the leaf headers hierarchically nested under this header. + +#### Returns + +[`Header`](../type-aliases/Header.md)\<`TFeatures`, `TData`, `TValue`\>[] + +#### Inherited from + +[`Header_Header`](Header_Header.md).[`getLeafHeaders`](Header_Header.md#getleafheaders) + +*** + +### headerGroup + +```ts +headerGroup: + | HeaderGroup + | null; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:69](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L69) + +The header's associated header group object. + +#### Inherited from + +[`Header_Header`](Header_Header.md).[`headerGroup`](Header_Header.md#headergroup) + +*** + +### id + +```ts +id: string; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:73](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L73) + +The unique identifier for the header. + +#### Inherited from + +[`Header_Header`](Header_Header.md).[`id`](Header_Header.md#id) + +*** + +### index + +```ts +index: number; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:77](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L77) + +The index for the header within the header group. + +#### Inherited from + +[`Header_Header`](Header_Header.md).[`index`](Header_Header.md#index) + +*** + +### isPlaceholder + +```ts +isPlaceholder: boolean; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:81](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L81) + +A boolean denoting if the header is a placeholder header. + +#### Inherited from + +[`Header_Header`](Header_Header.md).[`isPlaceholder`](Header_Header.md#isplaceholder) + +*** + +### placeholderId? + +```ts +optional placeholderId: string; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:85](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L85) + +If the header is a placeholder header, this will be a unique header ID that does not conflict with any other headers across the table. + +#### Inherited from + +[`Header_Header`](Header_Header.md).[`placeholderId`](Header_Header.md#placeholderid) + +*** + +### rowSpan + +```ts +rowSpan: number; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:89](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L89) + +The row-span for the header. + +#### Inherited from + +[`Header_Header`](Header_Header.md).[`rowSpan`](Header_Header.md#rowspan) + +*** + +### subHeaders + +```ts +subHeaders: Header[]; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:93](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L93) + +The header's hierarchical sub/child headers. Will be empty if the header's associated column is a leaf-column. + +#### Inherited from + +[`Header_Header`](Header_Header.md).[`subHeaders`](Header_Header.md#subheaders) + +*** + +### table + +```ts +table: Table; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:97](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L97) + +Reference to the parent table instance. + +#### Inherited from + +[`Header_Header`](Header_Header.md).[`table`](Header_Header.md#table) diff --git a/docs/reference/index/interfaces/Header_CoreProperties.md b/docs/reference/index/interfaces/Header_CoreProperties.md new file mode 100644 index 0000000000..c9510471c6 --- /dev/null +++ b/docs/reference/index/interfaces/Header_CoreProperties.md @@ -0,0 +1,160 @@ +--- +id: Header_CoreProperties +title: Header_CoreProperties +--- + +# Interface: Header\_CoreProperties\ + +Defined in: [core/headers/coreHeadersFeature.types.ts:49](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L49) + +## Extended by + +- [`Header_Header`](Header_Header.md) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](../type-aliases/CellData.md) = [`CellData`](../type-aliases/CellData.md) + +## Properties + +### colSpan + +```ts +colSpan: number; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:57](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L57) + +The col-span for the header. + +*** + +### column + +```ts +column: Column; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:61](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L61) + +The header's associated column object. + +*** + +### depth + +```ts +depth: number; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:65](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L65) + +The depth of the header, zero-indexed based. + +*** + +### headerGroup + +```ts +headerGroup: + | HeaderGroup + | null; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:69](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L69) + +The header's associated header group object. + +*** + +### id + +```ts +id: string; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:73](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L73) + +The unique identifier for the header. + +*** + +### index + +```ts +index: number; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:77](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L77) + +The index for the header within the header group. + +*** + +### isPlaceholder + +```ts +isPlaceholder: boolean; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:81](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L81) + +A boolean denoting if the header is a placeholder header. + +*** + +### placeholderId? + +```ts +optional placeholderId: string; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:85](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L85) + +If the header is a placeholder header, this will be a unique header ID that does not conflict with any other headers across the table. + +*** + +### rowSpan + +```ts +rowSpan: number; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:89](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L89) + +The row-span for the header. + +*** + +### subHeaders + +```ts +subHeaders: Header[]; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:93](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L93) + +The header's hierarchical sub/child headers. Will be empty if the header's associated column is a leaf-column. + +*** + +### table + +```ts +table: Table; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:97](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L97) + +Reference to the parent table instance. diff --git a/docs/reference/index/interfaces/Header_Header.md b/docs/reference/index/interfaces/Header_Header.md new file mode 100644 index 0000000000..ce794fb2bb --- /dev/null +++ b/docs/reference/index/interfaces/Header_Header.md @@ -0,0 +1,240 @@ +--- +id: Header_Header +title: Header_Header +--- + +# Interface: Header\_Header\ + +Defined in: [core/headers/coreHeadersFeature.types.ts:100](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L100) + +## Extends + +- [`Header_CoreProperties`](Header_CoreProperties.md)\<`TFeatures`, `TData`, `TValue`\> + +## Extended by + +- [`Header_Core`](Header_Core.md) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](../type-aliases/CellData.md) = [`CellData`](../type-aliases/CellData.md) + +## Properties + +### colSpan + +```ts +colSpan: number; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:57](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L57) + +The col-span for the header. + +#### Inherited from + +[`Header_CoreProperties`](Header_CoreProperties.md).[`colSpan`](Header_CoreProperties.md#colspan) + +*** + +### column + +```ts +column: Column; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:61](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L61) + +The header's associated column object. + +#### Inherited from + +[`Header_CoreProperties`](Header_CoreProperties.md).[`column`](Header_CoreProperties.md#column) + +*** + +### depth + +```ts +depth: number; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:65](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L65) + +The depth of the header, zero-indexed based. + +#### Inherited from + +[`Header_CoreProperties`](Header_CoreProperties.md).[`depth`](Header_CoreProperties.md#depth) + +*** + +### getContext() + +```ts +getContext: () => HeaderContext; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:108](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L108) + +Returns the rendering context (or props) for column-based components like headers, footers and filters. + +#### Returns + +[`HeaderContext`](HeaderContext.md)\<`TFeatures`, `TData`, `TValue`\> + +*** + +### getLeafHeaders() + +```ts +getLeafHeaders: () => Header[]; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:112](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L112) + +Returns the leaf headers hierarchically nested under this header. + +#### Returns + +[`Header`](../type-aliases/Header.md)\<`TFeatures`, `TData`, `TValue`\>[] + +*** + +### headerGroup + +```ts +headerGroup: + | HeaderGroup + | null; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:69](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L69) + +The header's associated header group object. + +#### Inherited from + +[`Header_CoreProperties`](Header_CoreProperties.md).[`headerGroup`](Header_CoreProperties.md#headergroup) + +*** + +### id + +```ts +id: string; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:73](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L73) + +The unique identifier for the header. + +#### Inherited from + +[`Header_CoreProperties`](Header_CoreProperties.md).[`id`](Header_CoreProperties.md#id) + +*** + +### index + +```ts +index: number; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:77](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L77) + +The index for the header within the header group. + +#### Inherited from + +[`Header_CoreProperties`](Header_CoreProperties.md).[`index`](Header_CoreProperties.md#index) + +*** + +### isPlaceholder + +```ts +isPlaceholder: boolean; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:81](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L81) + +A boolean denoting if the header is a placeholder header. + +#### Inherited from + +[`Header_CoreProperties`](Header_CoreProperties.md).[`isPlaceholder`](Header_CoreProperties.md#isplaceholder) + +*** + +### placeholderId? + +```ts +optional placeholderId: string; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:85](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L85) + +If the header is a placeholder header, this will be a unique header ID that does not conflict with any other headers across the table. + +#### Inherited from + +[`Header_CoreProperties`](Header_CoreProperties.md).[`placeholderId`](Header_CoreProperties.md#placeholderid) + +*** + +### rowSpan + +```ts +rowSpan: number; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:89](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L89) + +The row-span for the header. + +#### Inherited from + +[`Header_CoreProperties`](Header_CoreProperties.md).[`rowSpan`](Header_CoreProperties.md#rowspan) + +*** + +### subHeaders + +```ts +subHeaders: Header[]; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:93](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L93) + +The header's hierarchical sub/child headers. Will be empty if the header's associated column is a leaf-column. + +#### Inherited from + +[`Header_CoreProperties`](Header_CoreProperties.md).[`subHeaders`](Header_CoreProperties.md#subheaders) + +*** + +### table + +```ts +table: Table; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:97](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L97) + +Reference to the parent table instance. + +#### Inherited from + +[`Header_CoreProperties`](Header_CoreProperties.md).[`table`](Header_CoreProperties.md#table) diff --git a/docs/reference/index/interfaces/Header_Plugins.md b/docs/reference/index/interfaces/Header_Plugins.md new file mode 100644 index 0000000000..c1e09c4777 --- /dev/null +++ b/docs/reference/index/interfaces/Header_Plugins.md @@ -0,0 +1,25 @@ +--- +id: Header_Plugins +title: Header_Plugins +--- + +# Interface: Header\_Plugins\ + +Defined in: [types/Header.ts:11](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Header.ts#L11) + +Use this interface as a target for declaration merging to add your own plugin properties. +Note: This will affect the types of all tables in your project. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](../type-aliases/CellData.md) = [`CellData`](../type-aliases/CellData.md) diff --git a/docs/reference/index/interfaces/IdIdentifier.md b/docs/reference/index/interfaces/IdIdentifier.md new file mode 100644 index 0000000000..49bb4140c3 --- /dev/null +++ b/docs/reference/index/interfaces/IdIdentifier.md @@ -0,0 +1,42 @@ +--- +id: IdIdentifier +title: IdIdentifier +--- + +# Interface: IdIdentifier\ + +Defined in: [types/ColumnDef.ts:50](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L50) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](../type-aliases/CellData.md) = [`CellData`](../type-aliases/CellData.md) + +## Properties + +### header? + +```ts +optional header: ColumnDefTemplate>; +``` + +Defined in: [types/ColumnDef.ts:56](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L56) + +*** + +### id + +```ts +id: string; +``` + +Defined in: [types/ColumnDef.ts:55](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L55) diff --git a/docs/reference/index/interfaces/PaginationDefaultOptions.md b/docs/reference/index/interfaces/PaginationDefaultOptions.md new file mode 100644 index 0000000000..8c2f18bf68 --- /dev/null +++ b/docs/reference/index/interfaces/PaginationDefaultOptions.md @@ -0,0 +1,18 @@ +--- +id: PaginationDefaultOptions +title: PaginationDefaultOptions +--- + +# Interface: PaginationDefaultOptions + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:40](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L40) + +## Properties + +### onPaginationChange + +```ts +onPaginationChange: OnChangeFn; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:41](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L41) diff --git a/docs/reference/index/interfaces/PaginationState.md b/docs/reference/index/interfaces/PaginationState.md new file mode 100644 index 0000000000..9b07030209 --- /dev/null +++ b/docs/reference/index/interfaces/PaginationState.md @@ -0,0 +1,28 @@ +--- +id: PaginationState +title: PaginationState +--- + +# Interface: PaginationState + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:6](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L6) + +## Properties + +### pageIndex + +```ts +pageIndex: number; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:7](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L7) + +*** + +### pageSize + +```ts +pageSize: number; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:8](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L8) diff --git a/docs/reference/index/interfaces/Plugins.md b/docs/reference/index/interfaces/Plugins.md new file mode 100644 index 0000000000..a923a8a653 --- /dev/null +++ b/docs/reference/index/interfaces/Plugins.md @@ -0,0 +1,8 @@ +--- +id: Plugins +title: Plugins +--- + +# Interface: Plugins + +Defined in: [types/TableFeatures.ts:40](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L40) diff --git a/docs/reference/index/interfaces/PrototypeAPI.md b/docs/reference/index/interfaces/PrototypeAPI.md new file mode 100644 index 0000000000..0258cf4655 --- /dev/null +++ b/docs/reference/index/interfaces/PrototypeAPI.md @@ -0,0 +1,66 @@ +--- +id: PrototypeAPI +title: PrototypeAPI +--- + +# Interface: PrototypeAPI\ + +Defined in: [utils.ts:386](https://github.com/TanStack/table/blob/main/packages/table-core/src/utils.ts#L386) + +## Type Parameters + +### TDeps + +`TDeps` *extends* `ReadonlyArray`\<`any`\> + +### TDepArgs + +`TDepArgs` + +## Properties + +### fn() + +```ts +fn: (self, ...args) => any; +``` + +Defined in: [utils.ts:387](https://github.com/TanStack/table/blob/main/packages/table-core/src/utils.ts#L387) + +#### Parameters + +##### self + +`any` + +##### args + +...`any` + +#### Returns + +`any` + +*** + +### memoDeps()? + +```ts +optional memoDeps: (self, depArgs?) => any[] | undefined; +``` + +Defined in: [utils.ts:388](https://github.com/TanStack/table/blob/main/packages/table-core/src/utils.ts#L388) + +#### Parameters + +##### self + +`any` + +##### depArgs? + +`any` + +#### Returns + +`any`[] \| `undefined` diff --git a/docs/reference/index/interfaces/ResolvedColumnFilter.md b/docs/reference/index/interfaces/ResolvedColumnFilter.md new file mode 100644 index 0000000000..121659ae70 --- /dev/null +++ b/docs/reference/index/interfaces/ResolvedColumnFilter.md @@ -0,0 +1,48 @@ +--- +id: ResolvedColumnFilter +title: ResolvedColumnFilter +--- + +# Interface: ResolvedColumnFilter\ + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:29](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L29) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### filterFn + +```ts +filterFn: FilterFn; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:33](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L33) + +*** + +### id + +```ts +id: string; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:34](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L34) + +*** + +### resolvedValue + +```ts +resolvedValue: unknown; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:35](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L35) diff --git a/docs/reference/index/interfaces/RowExpandingFeatureConstructors.md b/docs/reference/index/interfaces/RowExpandingFeatureConstructors.md new file mode 100644 index 0000000000..ca520ba3fe --- /dev/null +++ b/docs/reference/index/interfaces/RowExpandingFeatureConstructors.md @@ -0,0 +1,18 @@ +--- +id: RowExpandingFeatureConstructors +title: RowExpandingFeatureConstructors +--- + +# Interface: RowExpandingFeatureConstructors\ + +Defined in: [features/row-expanding/rowExpandingFeature.ts:34](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.ts#L34) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/RowModel.md b/docs/reference/index/interfaces/RowModel.md new file mode 100644 index 0000000000..299c831f98 --- /dev/null +++ b/docs/reference/index/interfaces/RowModel.md @@ -0,0 +1,48 @@ +--- +id: RowModel +title: RowModel +--- + +# Interface: RowModel\ + +Defined in: [core/row-models/coreRowModelsFeature.types.ts:12](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.types.ts#L12) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### flatRows + +```ts +flatRows: Row[]; +``` + +Defined in: [core/row-models/coreRowModelsFeature.types.ts:17](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.types.ts#L17) + +*** + +### rows + +```ts +rows: Row[]; +``` + +Defined in: [core/row-models/coreRowModelsFeature.types.ts:16](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.types.ts#L16) + +*** + +### rowsById + +```ts +rowsById: Record>; +``` + +Defined in: [core/row-models/coreRowModelsFeature.types.ts:18](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.types.ts#L18) diff --git a/docs/reference/index/interfaces/RowModelFns_ColumnFiltering.md b/docs/reference/index/interfaces/RowModelFns_ColumnFiltering.md new file mode 100644 index 0000000000..e6d142dc70 --- /dev/null +++ b/docs/reference/index/interfaces/RowModelFns_ColumnFiltering.md @@ -0,0 +1,28 @@ +--- +id: RowModelFns_ColumnFiltering +title: RowModelFns_ColumnFiltering +--- + +# Interface: RowModelFns\_ColumnFiltering\ + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:38](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L38) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### filterFns + +```ts +filterFns: Record>; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:42](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L42) diff --git a/docs/reference/index/interfaces/RowModelFns_ColumnGrouping.md b/docs/reference/index/interfaces/RowModelFns_ColumnGrouping.md new file mode 100644 index 0000000000..cf44182b79 --- /dev/null +++ b/docs/reference/index/interfaces/RowModelFns_ColumnGrouping.md @@ -0,0 +1,28 @@ +--- +id: RowModelFns_ColumnGrouping +title: RowModelFns_ColumnGrouping +--- + +# Interface: RowModelFns\_ColumnGrouping\ + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:21](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L21) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### aggregationFns + +```ts +aggregationFns: Record>; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:25](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L25) diff --git a/docs/reference/index/interfaces/RowModelFns_Core.md b/docs/reference/index/interfaces/RowModelFns_Core.md new file mode 100644 index 0000000000..61bf61fa25 --- /dev/null +++ b/docs/reference/index/interfaces/RowModelFns_Core.md @@ -0,0 +1,8 @@ +--- +id: RowModelFns_Core +title: RowModelFns_Core +--- + +# Interface: RowModelFns\_Core + +Defined in: [types/RowModelFns.ts:16](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/RowModelFns.ts#L16) diff --git a/docs/reference/index/interfaces/RowModelFns_Plugins.md b/docs/reference/index/interfaces/RowModelFns_Plugins.md new file mode 100644 index 0000000000..ea45a3438f --- /dev/null +++ b/docs/reference/index/interfaces/RowModelFns_Plugins.md @@ -0,0 +1,21 @@ +--- +id: RowModelFns_Plugins +title: RowModelFns_Plugins +--- + +# Interface: RowModelFns\_Plugins\ + +Defined in: [types/RowModelFns.ts:11](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/RowModelFns.ts#L11) + +Use this interface as a target for declaration merging to add your own plugin properties. +Note: This will affect the types of all tables in your project. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/RowModelFns_RowSorting.md b/docs/reference/index/interfaces/RowModelFns_RowSorting.md new file mode 100644 index 0000000000..4965e01c44 --- /dev/null +++ b/docs/reference/index/interfaces/RowModelFns_RowSorting.md @@ -0,0 +1,28 @@ +--- +id: RowModelFns_RowSorting +title: RowModelFns_RowSorting +--- + +# Interface: RowModelFns\_RowSorting\ + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:21](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L21) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### sortFns + +```ts +sortFns: Record>; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:25](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L25) diff --git a/docs/reference/index/interfaces/RowPaginationFeatureConstructors.md b/docs/reference/index/interfaces/RowPaginationFeatureConstructors.md new file mode 100644 index 0000000000..d1ea260310 --- /dev/null +++ b/docs/reference/index/interfaces/RowPaginationFeatureConstructors.md @@ -0,0 +1,18 @@ +--- +id: RowPaginationFeatureConstructors +title: RowPaginationFeatureConstructors +--- + +# Interface: RowPaginationFeatureConstructors\ + +Defined in: [features/row-pagination/rowPaginationFeature.ts:31](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.ts#L31) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/RowPinningDefaultOptions.md b/docs/reference/index/interfaces/RowPinningDefaultOptions.md new file mode 100644 index 0000000000..ac7f1f49a1 --- /dev/null +++ b/docs/reference/index/interfaces/RowPinningDefaultOptions.md @@ -0,0 +1,18 @@ +--- +id: RowPinningDefaultOptions +title: RowPinningDefaultOptions +--- + +# Interface: RowPinningDefaultOptions + +Defined in: [features/row-pinning/rowPinningFeature.types.ts:36](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.types.ts#L36) + +## Properties + +### onRowPinningChange + +```ts +onRowPinningChange: OnChangeFn; +``` + +Defined in: [features/row-pinning/rowPinningFeature.types.ts:37](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.types.ts#L37) diff --git a/docs/reference/index/interfaces/RowPinningFeatureConstructors.md b/docs/reference/index/interfaces/RowPinningFeatureConstructors.md new file mode 100644 index 0000000000..ffb2b79762 --- /dev/null +++ b/docs/reference/index/interfaces/RowPinningFeatureConstructors.md @@ -0,0 +1,18 @@ +--- +id: RowPinningFeatureConstructors +title: RowPinningFeatureConstructors +--- + +# Interface: RowPinningFeatureConstructors\ + +Defined in: [features/row-pinning/rowPinningFeature.ts:28](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.ts#L28) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/RowPinningState.md b/docs/reference/index/interfaces/RowPinningState.md new file mode 100644 index 0000000000..05688d6664 --- /dev/null +++ b/docs/reference/index/interfaces/RowPinningState.md @@ -0,0 +1,28 @@ +--- +id: RowPinningState +title: RowPinningState +--- + +# Interface: RowPinningState + +Defined in: [features/row-pinning/rowPinningFeature.types.ts:7](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.types.ts#L7) + +## Properties + +### bottom + +```ts +bottom: string[]; +``` + +Defined in: [features/row-pinning/rowPinningFeature.types.ts:8](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.types.ts#L8) + +*** + +### top + +```ts +top: string[]; +``` + +Defined in: [features/row-pinning/rowPinningFeature.types.ts:9](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.types.ts#L9) diff --git a/docs/reference/index/interfaces/RowSelectionFeatureConstructors.md b/docs/reference/index/interfaces/RowSelectionFeatureConstructors.md new file mode 100644 index 0000000000..aa38498b0c --- /dev/null +++ b/docs/reference/index/interfaces/RowSelectionFeatureConstructors.md @@ -0,0 +1,18 @@ +--- +id: RowSelectionFeatureConstructors +title: RowSelectionFeatureConstructors +--- + +# Interface: RowSelectionFeatureConstructors\ + +Defined in: [features/row-selection/rowSelectionFeature.ts:40](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.ts#L40) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/RowSortingFeatureConstructors.md b/docs/reference/index/interfaces/RowSortingFeatureConstructors.md new file mode 100644 index 0000000000..ca4b60e2c7 --- /dev/null +++ b/docs/reference/index/interfaces/RowSortingFeatureConstructors.md @@ -0,0 +1,18 @@ +--- +id: RowSortingFeatureConstructors +title: RowSortingFeatureConstructors +--- + +# Interface: RowSortingFeatureConstructors\ + +Defined in: [features/row-sorting/rowSortingFeature.ts:36](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.ts#L36) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/Row_ColumnFiltering.md b/docs/reference/index/interfaces/Row_ColumnFiltering.md new file mode 100644 index 0000000000..8e1e31304b --- /dev/null +++ b/docs/reference/index/interfaces/Row_ColumnFiltering.md @@ -0,0 +1,42 @@ +--- +id: Row_ColumnFiltering +title: Row_ColumnFiltering +--- + +# Interface: Row\_ColumnFiltering\ + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:129](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L129) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### columnFilters + +```ts +columnFilters: Record; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:136](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L136) + +The column filters map for the row. This object tracks whether a row is passing/failing specific filters by their column ID. + +*** + +### columnFiltersMeta + +```ts +columnFiltersMeta: Record; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:140](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L140) + +The column filters meta map for the row. This object tracks any filter meta for a row as optionally provided during the filtering process. diff --git a/docs/reference/index/interfaces/Row_ColumnGrouping.md b/docs/reference/index/interfaces/Row_ColumnGrouping.md new file mode 100644 index 0000000000..b49d60f8ce --- /dev/null +++ b/docs/reference/index/interfaces/Row_ColumnGrouping.md @@ -0,0 +1,80 @@ +--- +id: Row_ColumnGrouping +title: Row_ColumnGrouping +--- + +# Interface: Row\_ColumnGrouping + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:112](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L112) + +## Properties + +### \_groupingValuesCache + +```ts +_groupingValuesCache: Record; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:113](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L113) + +*** + +### getGroupingValue() + +```ts +getGroupingValue: (columnId) => unknown; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:117](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L117) + +Returns the grouping value for any row and column (including leaf rows). + +#### Parameters + +##### columnId + +`string` + +#### Returns + +`unknown` + +*** + +### getIsGrouped() + +```ts +getIsGrouped: () => boolean; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:121](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L121) + +Returns whether or not the row is currently grouped. + +#### Returns + +`boolean` + +*** + +### groupingColumnId? + +```ts +optional groupingColumnId: string; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:125](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L125) + +If this row is grouped, this is the id of the column that this row is grouped by. + +*** + +### groupingValue? + +```ts +optional groupingValue: unknown; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:129](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L129) + +If this row is grouped, this is the unique/shared value for the `groupingColumnId` for all of the rows in this group. diff --git a/docs/reference/index/interfaces/Row_ColumnPinning.md b/docs/reference/index/interfaces/Row_ColumnPinning.md new file mode 100644 index 0000000000..084e3fbcd7 --- /dev/null +++ b/docs/reference/index/interfaces/Row_ColumnPinning.md @@ -0,0 +1,66 @@ +--- +id: Row_ColumnPinning +title: Row_ColumnPinning +--- + +# Interface: Row\_ColumnPinning\ + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:62](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L62) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### getCenterVisibleCells() + +```ts +getCenterVisibleCells: () => Cell[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:69](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L69) + +Returns all center pinned (unpinned) leaf cells in the row. + +#### Returns + +[`Cell`](../type-aliases/Cell.md)\<`TFeatures`, `TData`, `unknown`\>[] + +*** + +### getLeftVisibleCells() + +```ts +getLeftVisibleCells: () => Cell[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:73](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L73) + +Returns all left pinned leaf cells in the row. + +#### Returns + +[`Cell`](../type-aliases/Cell.md)\<`TFeatures`, `TData`, `unknown`\>[] + +*** + +### getRightVisibleCells() + +```ts +getRightVisibleCells: () => Cell[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:77](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L77) + +Returns all right pinned leaf cells in the row. + +#### Returns + +[`Cell`](../type-aliases/Cell.md)\<`TFeatures`, `TData`, `unknown`\>[] diff --git a/docs/reference/index/interfaces/Row_ColumnVisibility.md b/docs/reference/index/interfaces/Row_ColumnVisibility.md new file mode 100644 index 0000000000..05fb9a2a99 --- /dev/null +++ b/docs/reference/index/interfaces/Row_ColumnVisibility.md @@ -0,0 +1,50 @@ +--- +id: Row_ColumnVisibility +title: Row_ColumnVisibility +--- + +# Interface: Row\_ColumnVisibility\ + +Defined in: [features/column-visibility/columnVisibilityFeature.types.ts:75](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.types.ts#L75) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### getAllVisibleCells() + +```ts +getAllVisibleCells: () => Cell[]; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.types.ts:82](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.types.ts#L82) + +Returns all cells for the row whose columns are currently visible. + +#### Returns + +[`Cell`](../type-aliases/Cell.md)\<`TFeatures`, `TData`, `unknown`\>[] + +*** + +### getVisibleCells() + +```ts +getVisibleCells: () => Cell[]; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.types.ts:86](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.types.ts#L86) + +Returns an array of cells that account for column visibility for the row. + +#### Returns + +[`Cell`](../type-aliases/Cell.md)\<`TFeatures`, `TData`, `unknown`\>[] diff --git a/docs/reference/index/interfaces/Row_Core.md b/docs/reference/index/interfaces/Row_Core.md new file mode 100644 index 0000000000..2a10923465 --- /dev/null +++ b/docs/reference/index/interfaces/Row_Core.md @@ -0,0 +1,372 @@ +--- +id: Row_Core +title: Row_Core +--- + +# Interface: Row\_Core\ + +Defined in: [types/Row.ts:21](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Row.ts#L21) + +## Extends + +- [`Row_Row`](Row_Row.md)\<`TFeatures`, `TData`\> + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### \_uniqueValuesCache + +```ts +_uniqueValuesCache: Record; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:11](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L11) + +#### Inherited from + +[`Row_Row`](Row_Row.md).[`_uniqueValuesCache`](Row_Row.md#_uniquevaluescache) + +*** + +### \_valuesCache + +```ts +_valuesCache: Record; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:12](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L12) + +#### Inherited from + +[`Row_Row`](Row_Row.md).[`_valuesCache`](Row_Row.md#_valuescache) + +*** + +### depth + +```ts +depth: number; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:16](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L16) + +The depth of the row (if nested or grouped) relative to the root row array. + +#### Inherited from + +[`Row_Row`](Row_Row.md).[`depth`](Row_Row.md#depth) + +*** + +### getAllCells() + +```ts +getAllCells: () => Cell[]; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:55](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L55) + +Returns all of the cells for the row. + +#### Returns + +[`Cell`](../type-aliases/Cell.md)\<`TFeatures`, `TData`, `unknown`\>[] + +#### Inherited from + +[`Row_Row`](Row_Row.md).[`getAllCells`](Row_Row.md#getallcells) + +*** + +### getAllCellsByColumnId() + +```ts +getAllCellsByColumnId: () => Record>; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:51](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L51) + +#### Returns + +`Record`\<`string`, [`Cell`](../type-aliases/Cell.md)\<`TFeatures`, `TData`, `unknown`\>\> + +#### Inherited from + +[`Row_Row`](Row_Row.md).[`getAllCellsByColumnId`](Row_Row.md#getallcellsbycolumnid) + +*** + +### getLeafRows() + +```ts +getLeafRows: () => Row[]; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:59](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L59) + +Returns the leaf rows for the row, not including any parent rows. + +#### Returns + +[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\>[] + +#### Inherited from + +[`Row_Row`](Row_Row.md).[`getLeafRows`](Row_Row.md#getleafrows) + +*** + +### getParentRow() + +```ts +getParentRow: () => Row | undefined; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:63](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L63) + +Returns the parent row for the row, if it exists. + +#### Returns + +[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\> \| `undefined` + +#### Inherited from + +[`Row_Row`](Row_Row.md).[`getParentRow`](Row_Row.md#getparentrow) + +*** + +### getParentRows() + +```ts +getParentRows: () => Row[]; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:67](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L67) + +Returns the parent rows for the row, all the way up to a root row. + +#### Returns + +[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\>[] + +#### Inherited from + +[`Row_Row`](Row_Row.md).[`getParentRows`](Row_Row.md#getparentrows) + +*** + +### getUniqueValues() + +```ts +getUniqueValues: (columnId) => TValue[]; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:71](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L71) + +Returns a unique array of values from the row for a given columnId. + +#### Type Parameters + +##### TValue + +`TValue` + +#### Parameters + +##### columnId + +`string` + +#### Returns + +`TValue`[] + +#### Inherited from + +[`Row_Row`](Row_Row.md).[`getUniqueValues`](Row_Row.md#getuniquevalues) + +*** + +### getValue() + +```ts +getValue: (columnId) => TValue; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:75](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L75) + +Returns the value from the row for a given columnId. + +#### Type Parameters + +##### TValue + +`TValue` + +#### Parameters + +##### columnId + +`string` + +#### Returns + +`TValue` + +#### Inherited from + +[`Row_Row`](Row_Row.md).[`getValue`](Row_Row.md#getvalue) + +*** + +### id + +```ts +id: string; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L20) + +The resolved unique identifier for the row resolved via the `options.getRowId` option. Defaults to the row's index (or relative index if it is a subRow). + +#### Inherited from + +[`Row_Row`](Row_Row.md).[`id`](Row_Row.md#id) + +*** + +### index + +```ts +index: number; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L24) + +The index of the row within its parent array (or the root data array). + +#### Inherited from + +[`Row_Row`](Row_Row.md).[`index`](Row_Row.md#index) + +*** + +### original + +```ts +original: TData; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:28](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L28) + +The original row object provided to the table. If the row is a grouped row, the original row object will be the first original in the group. + +#### Inherited from + +[`Row_Row`](Row_Row.md).[`original`](Row_Row.md#original) + +*** + +### originalSubRows? + +```ts +optional originalSubRows: readonly TData[]; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:32](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L32) + +An array of the original subRows as returned by the `options.getSubRows` option. + +#### Inherited from + +[`Row_Row`](Row_Row.md).[`originalSubRows`](Row_Row.md#originalsubrows) + +*** + +### parentId? + +```ts +optional parentId: string; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:36](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L36) + +If nested, this row's parent row id. + +#### Inherited from + +[`Row_Row`](Row_Row.md).[`parentId`](Row_Row.md#parentid) + +*** + +### renderValue() + +```ts +renderValue: (columnId) => TValue; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:79](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L79) + +Renders the value for the row in a given columnId the same as `getValue`, but will return the `renderFallbackValue` if no value is found. + +#### Type Parameters + +##### TValue + +`TValue` + +#### Parameters + +##### columnId + +`string` + +#### Returns + +`TValue` + +#### Inherited from + +[`Row_Row`](Row_Row.md).[`renderValue`](Row_Row.md#rendervalue) + +*** + +### subRows + +```ts +subRows: Row[]; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:40](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L40) + +An array of subRows for the row as returned and created by the `options.getSubRows` option. + +#### Inherited from + +[`Row_Row`](Row_Row.md).[`subRows`](Row_Row.md#subrows) + +*** + +### table + +```ts +table: Table_Internal; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:44](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L44) + +Reference to the parent table instance. + +#### Inherited from + +[`Row_Row`](Row_Row.md).[`table`](Row_Row.md#table) diff --git a/docs/reference/index/interfaces/Row_CoreProperties.md b/docs/reference/index/interfaces/Row_CoreProperties.md new file mode 100644 index 0000000000..9c3736a03f --- /dev/null +++ b/docs/reference/index/interfaces/Row_CoreProperties.md @@ -0,0 +1,138 @@ +--- +id: Row_CoreProperties +title: Row_CoreProperties +--- + +# Interface: Row\_CoreProperties\ + +Defined in: [core/rows/coreRowsFeature.types.ts:7](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L7) + +## Extended by + +- [`Row_Row`](Row_Row.md) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### \_uniqueValuesCache + +```ts +_uniqueValuesCache: Record; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:11](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L11) + +*** + +### \_valuesCache + +```ts +_valuesCache: Record; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:12](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L12) + +*** + +### depth + +```ts +depth: number; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:16](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L16) + +The depth of the row (if nested or grouped) relative to the root row array. + +*** + +### id + +```ts +id: string; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L20) + +The resolved unique identifier for the row resolved via the `options.getRowId` option. Defaults to the row's index (or relative index if it is a subRow). + +*** + +### index + +```ts +index: number; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L24) + +The index of the row within its parent array (or the root data array). + +*** + +### original + +```ts +original: TData; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:28](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L28) + +The original row object provided to the table. If the row is a grouped row, the original row object will be the first original in the group. + +*** + +### originalSubRows? + +```ts +optional originalSubRows: readonly TData[]; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:32](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L32) + +An array of the original subRows as returned by the `options.getSubRows` option. + +*** + +### parentId? + +```ts +optional parentId: string; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:36](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L36) + +If nested, this row's parent row id. + +*** + +### subRows + +```ts +subRows: Row[]; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:40](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L40) + +An array of subRows for the row as returned and created by the `options.getSubRows` option. + +*** + +### table + +```ts +table: Table_Internal; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:44](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L44) + +Reference to the parent table instance. diff --git a/docs/reference/index/interfaces/Row_Plugins.md b/docs/reference/index/interfaces/Row_Plugins.md new file mode 100644 index 0000000000..2c6aa8f40b --- /dev/null +++ b/docs/reference/index/interfaces/Row_Plugins.md @@ -0,0 +1,21 @@ +--- +id: Row_Plugins +title: Row_Plugins +--- + +# Interface: Row\_Plugins\ + +Defined in: [types/Row.ts:16](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Row.ts#L16) + +Use this interface as a target for declaration merging to add your own plugin properties. +Note: This will affect the types of all tables in your project. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/Row_Row.md b/docs/reference/index/interfaces/Row_Row.md new file mode 100644 index 0000000000..8cd051c2bd --- /dev/null +++ b/docs/reference/index/interfaces/Row_Row.md @@ -0,0 +1,344 @@ +--- +id: Row_Row +title: Row_Row +--- + +# Interface: Row\_Row\ + +Defined in: [core/rows/coreRowsFeature.types.ts:47](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L47) + +## Extends + +- [`Row_CoreProperties`](Row_CoreProperties.md)\<`TFeatures`, `TData`\> + +## Extended by + +- [`Row_Core`](Row_Core.md) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### \_uniqueValuesCache + +```ts +_uniqueValuesCache: Record; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:11](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L11) + +#### Inherited from + +[`Row_CoreProperties`](Row_CoreProperties.md).[`_uniqueValuesCache`](Row_CoreProperties.md#_uniquevaluescache) + +*** + +### \_valuesCache + +```ts +_valuesCache: Record; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:12](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L12) + +#### Inherited from + +[`Row_CoreProperties`](Row_CoreProperties.md).[`_valuesCache`](Row_CoreProperties.md#_valuescache) + +*** + +### depth + +```ts +depth: number; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:16](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L16) + +The depth of the row (if nested or grouped) relative to the root row array. + +#### Inherited from + +[`Row_CoreProperties`](Row_CoreProperties.md).[`depth`](Row_CoreProperties.md#depth) + +*** + +### getAllCells() + +```ts +getAllCells: () => Cell[]; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:55](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L55) + +Returns all of the cells for the row. + +#### Returns + +[`Cell`](../type-aliases/Cell.md)\<`TFeatures`, `TData`, `unknown`\>[] + +*** + +### getAllCellsByColumnId() + +```ts +getAllCellsByColumnId: () => Record>; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:51](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L51) + +#### Returns + +`Record`\<`string`, [`Cell`](../type-aliases/Cell.md)\<`TFeatures`, `TData`, `unknown`\>\> + +*** + +### getLeafRows() + +```ts +getLeafRows: () => Row[]; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:59](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L59) + +Returns the leaf rows for the row, not including any parent rows. + +#### Returns + +[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\>[] + +*** + +### getParentRow() + +```ts +getParentRow: () => Row | undefined; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:63](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L63) + +Returns the parent row for the row, if it exists. + +#### Returns + +[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\> \| `undefined` + +*** + +### getParentRows() + +```ts +getParentRows: () => Row[]; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:67](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L67) + +Returns the parent rows for the row, all the way up to a root row. + +#### Returns + +[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\>[] + +*** + +### getUniqueValues() + +```ts +getUniqueValues: (columnId) => TValue[]; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:71](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L71) + +Returns a unique array of values from the row for a given columnId. + +#### Type Parameters + +##### TValue + +`TValue` + +#### Parameters + +##### columnId + +`string` + +#### Returns + +`TValue`[] + +*** + +### getValue() + +```ts +getValue: (columnId) => TValue; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:75](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L75) + +Returns the value from the row for a given columnId. + +#### Type Parameters + +##### TValue + +`TValue` + +#### Parameters + +##### columnId + +`string` + +#### Returns + +`TValue` + +*** + +### id + +```ts +id: string; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L20) + +The resolved unique identifier for the row resolved via the `options.getRowId` option. Defaults to the row's index (or relative index if it is a subRow). + +#### Inherited from + +[`Row_CoreProperties`](Row_CoreProperties.md).[`id`](Row_CoreProperties.md#id) + +*** + +### index + +```ts +index: number; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L24) + +The index of the row within its parent array (or the root data array). + +#### Inherited from + +[`Row_CoreProperties`](Row_CoreProperties.md).[`index`](Row_CoreProperties.md#index) + +*** + +### original + +```ts +original: TData; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:28](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L28) + +The original row object provided to the table. If the row is a grouped row, the original row object will be the first original in the group. + +#### Inherited from + +[`Row_CoreProperties`](Row_CoreProperties.md).[`original`](Row_CoreProperties.md#original) + +*** + +### originalSubRows? + +```ts +optional originalSubRows: readonly TData[]; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:32](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L32) + +An array of the original subRows as returned by the `options.getSubRows` option. + +#### Inherited from + +[`Row_CoreProperties`](Row_CoreProperties.md).[`originalSubRows`](Row_CoreProperties.md#originalsubrows) + +*** + +### parentId? + +```ts +optional parentId: string; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:36](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L36) + +If nested, this row's parent row id. + +#### Inherited from + +[`Row_CoreProperties`](Row_CoreProperties.md).[`parentId`](Row_CoreProperties.md#parentid) + +*** + +### renderValue() + +```ts +renderValue: (columnId) => TValue; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:79](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L79) + +Renders the value for the row in a given columnId the same as `getValue`, but will return the `renderFallbackValue` if no value is found. + +#### Type Parameters + +##### TValue + +`TValue` + +#### Parameters + +##### columnId + +`string` + +#### Returns + +`TValue` + +*** + +### subRows + +```ts +subRows: Row[]; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:40](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L40) + +An array of subRows for the row as returned and created by the `options.getSubRows` option. + +#### Inherited from + +[`Row_CoreProperties`](Row_CoreProperties.md).[`subRows`](Row_CoreProperties.md#subrows) + +*** + +### table + +```ts +table: Table_Internal; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:44](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L44) + +Reference to the parent table instance. + +#### Inherited from + +[`Row_CoreProperties`](Row_CoreProperties.md).[`table`](Row_CoreProperties.md#table) diff --git a/docs/reference/index/interfaces/Row_RowExpanding.md b/docs/reference/index/interfaces/Row_RowExpanding.md new file mode 100644 index 0000000000..3f6cbb0431 --- /dev/null +++ b/docs/reference/index/interfaces/Row_RowExpanding.md @@ -0,0 +1,100 @@ +--- +id: Row_RowExpanding +title: Row_RowExpanding +--- + +# Interface: Row\_RowExpanding + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:14](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L14) + +## Properties + +### getCanExpand() + +```ts +getCanExpand: () => boolean; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:18](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L18) + +Returns whether the row can be expanded. + +#### Returns + +`boolean` + +*** + +### getIsAllParentsExpanded() + +```ts +getIsAllParentsExpanded: () => boolean; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:22](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L22) + +Returns whether all parent rows of the row are expanded. + +#### Returns + +`boolean` + +*** + +### getIsExpanded() + +```ts +getIsExpanded: () => boolean; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:26](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L26) + +Returns whether the row is expanded. + +#### Returns + +`boolean` + +*** + +### getToggleExpandedHandler() + +```ts +getToggleExpandedHandler: () => () => void; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:30](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L30) + +Returns a function that can be used to toggle the expanded state of the row. This function can be used to bind to an event handler to a button. + +#### Returns + +```ts +(): void; +``` + +##### Returns + +`void` + +*** + +### toggleExpanded() + +```ts +toggleExpanded: (expanded?) => void; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:34](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L34) + +Toggles the expanded state (or sets it if `expanded` is provided) for the row. + +#### Parameters + +##### expanded? + +`boolean` + +#### Returns + +`void` diff --git a/docs/reference/index/interfaces/Row_RowPinning.md b/docs/reference/index/interfaces/Row_RowPinning.md new file mode 100644 index 0000000000..08104a36ce --- /dev/null +++ b/docs/reference/index/interfaces/Row_RowPinning.md @@ -0,0 +1,86 @@ +--- +id: Row_RowPinning +title: Row_RowPinning +--- + +# Interface: Row\_RowPinning + +Defined in: [features/row-pinning/rowPinningFeature.types.ts:40](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.types.ts#L40) + +## Properties + +### getCanPin() + +```ts +getCanPin: () => boolean; +``` + +Defined in: [features/row-pinning/rowPinningFeature.types.ts:44](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.types.ts#L44) + +Returns whether or not the row can be pinned. + +#### Returns + +`boolean` + +*** + +### getIsPinned() + +```ts +getIsPinned: () => RowPinningPosition; +``` + +Defined in: [features/row-pinning/rowPinningFeature.types.ts:48](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.types.ts#L48) + +Returns the pinned position of the row. (`'top'`, `'bottom'` or `false`) + +#### Returns + +[`RowPinningPosition`](../type-aliases/RowPinningPosition.md) + +*** + +### getPinnedIndex() + +```ts +getPinnedIndex: () => number; +``` + +Defined in: [features/row-pinning/rowPinningFeature.types.ts:52](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.types.ts#L52) + +Returns the numeric pinned index of the row within a pinned row group. + +#### Returns + +`number` + +*** + +### pin() + +```ts +pin: (position, includeLeafRows?, includeParentRows?) => void; +``` + +Defined in: [features/row-pinning/rowPinningFeature.types.ts:56](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.types.ts#L56) + +Pins a row to the `'top'` or `'bottom'`, or unpins the row to the center if `false` is passed. + +#### Parameters + +##### position + +[`RowPinningPosition`](../type-aliases/RowPinningPosition.md) + +##### includeLeafRows? + +`boolean` + +##### includeParentRows? + +`boolean` + +#### Returns + +`void` diff --git a/docs/reference/index/interfaces/Row_RowSelection.md b/docs/reference/index/interfaces/Row_RowSelection.md new file mode 100644 index 0000000000..3bc9543911 --- /dev/null +++ b/docs/reference/index/interfaces/Row_RowSelection.md @@ -0,0 +1,160 @@ +--- +id: Row_RowSelection +title: Row_RowSelection +--- + +# Interface: Row\_RowSelection + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:50](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L50) + +## Properties + +### getCanMultiSelect() + +```ts +getCanMultiSelect: () => boolean; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:54](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L54) + +Returns whether or not the row can multi-select. + +#### Returns + +`boolean` + +*** + +### getCanSelect() + +```ts +getCanSelect: () => boolean; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:58](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L58) + +Returns whether or not the row can be selected. + +#### Returns + +`boolean` + +*** + +### getCanSelectSubRows() + +```ts +getCanSelectSubRows: () => boolean; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:62](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L62) + +Returns whether or not the row can select sub rows automatically when the parent row is selected. + +#### Returns + +`boolean` + +*** + +### getIsAllSubRowsSelected() + +```ts +getIsAllSubRowsSelected: () => boolean; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:66](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L66) + +Returns whether or not all of the row's sub rows are selected. + +#### Returns + +`boolean` + +*** + +### getIsSelected() + +```ts +getIsSelected: () => boolean; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:70](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L70) + +Returns whether or not the row is selected. + +#### Returns + +`boolean` + +*** + +### getIsSomeSelected() + +```ts +getIsSomeSelected: () => boolean; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:74](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L74) + +Returns whether or not some of the row's sub rows are selected. + +#### Returns + +`boolean` + +*** + +### getToggleSelectedHandler() + +```ts +getToggleSelectedHandler: () => (event) => void; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:78](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L78) + +Returns a handler that can be used to toggle the row. + +#### Returns + +```ts +(event): void; +``` + +##### Parameters + +###### event + +`unknown` + +##### Returns + +`void` + +*** + +### toggleSelected() + +```ts +toggleSelected: (value?, opts?) => void; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:82](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L82) + +Selects/deselects the row. + +#### Parameters + +##### value? + +`boolean` + +##### opts? + +###### selectChildren? + +`boolean` + +#### Returns + +`void` diff --git a/docs/reference/index/interfaces/SortFn.md b/docs/reference/index/interfaces/SortFn.md new file mode 100644 index 0000000000..0b8acdf8f9 --- /dev/null +++ b/docs/reference/index/interfaces/SortFn.md @@ -0,0 +1,45 @@ +--- +id: SortFn +title: SortFn +--- + +# Interface: SortFn()\ + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:30](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L30) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +```ts +SortFn( + rowA, + rowB, + columnId): number; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:34](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L34) + +## Parameters + +### rowA + +[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\> + +### rowB + +[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\> + +### columnId + +`string` + +## Returns + +`number` diff --git a/docs/reference/index/interfaces/SortFns.md b/docs/reference/index/interfaces/SortFns.md new file mode 100644 index 0000000000..ded705adcf --- /dev/null +++ b/docs/reference/index/interfaces/SortFns.md @@ -0,0 +1,8 @@ +--- +id: SortFns +title: SortFns +--- + +# Interface: SortFns + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:28](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L28) diff --git a/docs/reference/index/interfaces/StockFeatures.md b/docs/reference/index/interfaces/StockFeatures.md new file mode 100644 index 0000000000..c8a8f99e48 --- /dev/null +++ b/docs/reference/index/interfaces/StockFeatures.md @@ -0,0 +1,148 @@ +--- +id: StockFeatures +title: StockFeatures +--- + +# Interface: StockFeatures + +Defined in: [features/stockFeatures.ts:16](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L16) + +## Properties + +### columnFacetingFeature + +```ts +columnFacetingFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:17](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L17) + +*** + +### columnFilteringFeature + +```ts +columnFilteringFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:18](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L18) + +*** + +### columnGroupingFeature + +```ts +columnGroupingFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:19](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L19) + +*** + +### columnOrderingFeature + +```ts +columnOrderingFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L20) + +*** + +### columnPinningFeature + +```ts +columnPinningFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:21](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L21) + +*** + +### columnResizingFeature + +```ts +columnResizingFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:22](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L22) + +*** + +### columnSizingFeature + +```ts +columnSizingFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:23](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L23) + +*** + +### columnVisibilityFeature + +```ts +columnVisibilityFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L24) + +*** + +### globalFilteringFeature + +```ts +globalFilteringFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:25](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L25) + +*** + +### rowExpandingFeature + +```ts +rowExpandingFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:26](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L26) + +*** + +### rowPaginationFeature + +```ts +rowPaginationFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:27](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L27) + +*** + +### rowPinningFeature + +```ts +rowPinningFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:28](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L28) + +*** + +### rowSelectionFeature + +```ts +rowSelectionFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:29](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L29) + +*** + +### rowSortingFeature + +```ts +rowSortingFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:30](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L30) diff --git a/docs/reference/index/interfaces/StringHeaderIdentifier.md b/docs/reference/index/interfaces/StringHeaderIdentifier.md new file mode 100644 index 0000000000..6b5b0a6d8a --- /dev/null +++ b/docs/reference/index/interfaces/StringHeaderIdentifier.md @@ -0,0 +1,28 @@ +--- +id: StringHeaderIdentifier +title: StringHeaderIdentifier +--- + +# Interface: StringHeaderIdentifier + +Defined in: [types/ColumnDef.ts:45](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L45) + +## Properties + +### header + +```ts +header: string; +``` + +Defined in: [types/ColumnDef.ts:46](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L46) + +*** + +### id? + +```ts +optional id: string; +``` + +Defined in: [types/ColumnDef.ts:47](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L47) diff --git a/docs/reference/index/interfaces/TableFeature.md b/docs/reference/index/interfaces/TableFeature.md new file mode 100644 index 0000000000..19f89d8a5f --- /dev/null +++ b/docs/reference/index/interfaces/TableFeature.md @@ -0,0 +1,122 @@ +--- +id: TableFeature +title: TableFeature +--- + +# Interface: TableFeature\ + +Defined in: [types/TableFeatures.ts:116](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L116) + +## Type Parameters + +### TConstructors + +`TConstructors` *extends* [`FeatureConstructors`](FeatureConstructors.md) + +## Properties + +### assignCellPrototype? + +```ts +optional assignCellPrototype: AssignCellPrototype; +``` + +Defined in: [types/TableFeatures.ts:121](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L121) + +Assigns Cell APIs to the cell prototype for memory-efficient method sharing. +This is called once per table to build a shared prototype for all cells. + +*** + +### assignColumnPrototype? + +```ts +optional assignColumnPrototype: AssignColumnPrototype; +``` + +Defined in: [types/TableFeatures.ts:126](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L126) + +Assigns Column APIs to the column prototype for memory-efficient method sharing. +This is called once per table to build a shared prototype for all columns. + +*** + +### assignHeaderPrototype? + +```ts +optional assignHeaderPrototype: AssignHeaderPrototype; +``` + +Defined in: [types/TableFeatures.ts:131](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L131) + +Assigns Header APIs to the header prototype for memory-efficient method sharing. +This is called once per table to build a shared prototype for all headers. + +*** + +### assignRowPrototype? + +```ts +optional assignRowPrototype: AssignRowPrototype; +``` + +Defined in: [types/TableFeatures.ts:136](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L136) + +Assigns Row APIs to the row prototype for memory-efficient method sharing. +This is called once per table to build a shared prototype for all rows. + +*** + +### constructTableAPIs? + +```ts +optional constructTableAPIs: ConstructTableAPIs; +``` + +Defined in: [types/TableFeatures.ts:141](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L141) + +Assigns Table APIs to the table instance. +Unlike row/cell/column/header, the table is a singleton so methods are assigned directly. + +*** + +### getDefaultColumnDef? + +```ts +optional getDefaultColumnDef: GetDefaultColumnDef; +``` + +Defined in: [types/TableFeatures.ts:142](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L142) + +*** + +### getDefaultTableOptions? + +```ts +optional getDefaultTableOptions: GetDefaultTableOptions; +``` + +Defined in: [types/TableFeatures.ts:143](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L143) + +*** + +### getInitialState? + +```ts +optional getInitialState: GetInitialState; +``` + +Defined in: [types/TableFeatures.ts:144](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L144) + +*** + +### initRowInstanceData? + +```ts +optional initRowInstanceData: InitRowInstanceData; +``` + +Defined in: [types/TableFeatures.ts:149](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L149) + +Initializes instance-specific data on each row (e.g., caches). +Methods should be assigned via assignRowPrototype instead. diff --git a/docs/reference/index/interfaces/TableFeatures.md b/docs/reference/index/interfaces/TableFeatures.md new file mode 100644 index 0000000000..6204ceb9ea --- /dev/null +++ b/docs/reference/index/interfaces/TableFeatures.md @@ -0,0 +1,306 @@ +--- +id: TableFeatures +title: TableFeatures +--- + +# Interface: TableFeatures + +Defined in: [types/TableFeatures.ts:42](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L42) + +## Extends + +- `Partial`\<[`CoreFeatures`](CoreFeatures.md)\>.`Partial`\<[`StockFeatures`](StockFeatures.md)\>.`Partial`\<[`Plugins`](Plugins.md)\> + +## Properties + +### columnFacetingFeature? + +```ts +optional columnFacetingFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:17](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L17) + +#### Inherited from + +[`StockFeatures`](StockFeatures.md).[`columnFacetingFeature`](StockFeatures.md#columnfacetingfeature) + +*** + +### columnFilteringFeature? + +```ts +optional columnFilteringFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:18](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L18) + +#### Inherited from + +[`StockFeatures`](StockFeatures.md).[`columnFilteringFeature`](StockFeatures.md#columnfilteringfeature) + +*** + +### columnGroupingFeature? + +```ts +optional columnGroupingFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:19](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L19) + +#### Inherited from + +[`StockFeatures`](StockFeatures.md).[`columnGroupingFeature`](StockFeatures.md#columngroupingfeature) + +*** + +### columnOrderingFeature? + +```ts +optional columnOrderingFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L20) + +#### Inherited from + +[`StockFeatures`](StockFeatures.md).[`columnOrderingFeature`](StockFeatures.md#columnorderingfeature) + +*** + +### columnPinningFeature? + +```ts +optional columnPinningFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:21](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L21) + +#### Inherited from + +[`StockFeatures`](StockFeatures.md).[`columnPinningFeature`](StockFeatures.md#columnpinningfeature) + +*** + +### columnResizingFeature? + +```ts +optional columnResizingFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:22](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L22) + +#### Inherited from + +[`StockFeatures`](StockFeatures.md).[`columnResizingFeature`](StockFeatures.md#columnresizingfeature) + +*** + +### columnSizingFeature? + +```ts +optional columnSizingFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:23](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L23) + +#### Inherited from + +[`StockFeatures`](StockFeatures.md).[`columnSizingFeature`](StockFeatures.md#columnsizingfeature) + +*** + +### columnVisibilityFeature? + +```ts +optional columnVisibilityFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L24) + +#### Inherited from + +[`StockFeatures`](StockFeatures.md).[`columnVisibilityFeature`](StockFeatures.md#columnvisibilityfeature) + +*** + +### coreCellsFeature? + +```ts +optional coreCellsFeature: TableFeature>; +``` + +Defined in: [core/coreFeatures.ts:11](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/coreFeatures.ts#L11) + +#### Inherited from + +[`CoreFeatures`](CoreFeatures.md).[`coreCellsFeature`](CoreFeatures.md#corecellsfeature) + +*** + +### coreColumnsFeature? + +```ts +optional coreColumnsFeature: TableFeature>; +``` + +Defined in: [core/coreFeatures.ts:12](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/coreFeatures.ts#L12) + +#### Inherited from + +[`CoreFeatures`](CoreFeatures.md).[`coreColumnsFeature`](CoreFeatures.md#corecolumnsfeature) + +*** + +### coreHeadersFeature? + +```ts +optional coreHeadersFeature: TableFeature>; +``` + +Defined in: [core/coreFeatures.ts:13](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/coreFeatures.ts#L13) + +#### Inherited from + +[`CoreFeatures`](CoreFeatures.md).[`coreHeadersFeature`](CoreFeatures.md#coreheadersfeature) + +*** + +### coreReativityFeature? + +```ts +optional coreReativityFeature: TableReactivityBindings; +``` + +Defined in: [core/coreFeatures.ts:10](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/coreFeatures.ts#L10) + +#### Inherited from + +[`CoreFeatures`](CoreFeatures.md).[`coreReativityFeature`](CoreFeatures.md#corereativityfeature) + +*** + +### coreRowModelsFeature? + +```ts +optional coreRowModelsFeature: TableFeature>; +``` + +Defined in: [core/coreFeatures.ts:14](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/coreFeatures.ts#L14) + +#### Inherited from + +[`CoreFeatures`](CoreFeatures.md).[`coreRowModelsFeature`](CoreFeatures.md#corerowmodelsfeature) + +*** + +### coreRowsFeature? + +```ts +optional coreRowsFeature: TableFeature>; +``` + +Defined in: [core/coreFeatures.ts:15](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/coreFeatures.ts#L15) + +#### Inherited from + +[`CoreFeatures`](CoreFeatures.md).[`coreRowsFeature`](CoreFeatures.md#corerowsfeature) + +*** + +### coreTablesFeature? + +```ts +optional coreTablesFeature: TableFeature>; +``` + +Defined in: [core/coreFeatures.ts:16](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/coreFeatures.ts#L16) + +#### Inherited from + +[`CoreFeatures`](CoreFeatures.md).[`coreTablesFeature`](CoreFeatures.md#coretablesfeature) + +*** + +### globalFilteringFeature? + +```ts +optional globalFilteringFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:25](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L25) + +#### Inherited from + +[`StockFeatures`](StockFeatures.md).[`globalFilteringFeature`](StockFeatures.md#globalfilteringfeature) + +*** + +### rowExpandingFeature? + +```ts +optional rowExpandingFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:26](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L26) + +#### Inherited from + +[`StockFeatures`](StockFeatures.md).[`rowExpandingFeature`](StockFeatures.md#rowexpandingfeature) + +*** + +### rowPaginationFeature? + +```ts +optional rowPaginationFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:27](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L27) + +#### Inherited from + +[`StockFeatures`](StockFeatures.md).[`rowPaginationFeature`](StockFeatures.md#rowpaginationfeature) + +*** + +### rowPinningFeature? + +```ts +optional rowPinningFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:28](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L28) + +#### Inherited from + +[`StockFeatures`](StockFeatures.md).[`rowPinningFeature`](StockFeatures.md#rowpinningfeature) + +*** + +### rowSelectionFeature? + +```ts +optional rowSelectionFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:29](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L29) + +#### Inherited from + +[`StockFeatures`](StockFeatures.md).[`rowSelectionFeature`](StockFeatures.md#rowselectionfeature) + +*** + +### rowSortingFeature? + +```ts +optional rowSortingFeature: TableFeature>; +``` + +Defined in: [features/stockFeatures.ts:30](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L30) + +#### Inherited from + +[`StockFeatures`](StockFeatures.md).[`rowSortingFeature`](StockFeatures.md#rowsortingfeature) diff --git a/docs/reference/index/interfaces/TableMeta.md b/docs/reference/index/interfaces/TableMeta.md new file mode 100644 index 0000000000..22408a0d51 --- /dev/null +++ b/docs/reference/index/interfaces/TableMeta.md @@ -0,0 +1,18 @@ +--- +id: TableMeta +title: TableMeta +--- + +# Interface: TableMeta\ + +Defined in: [core/table/coreTablesFeature.types.ts:11](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L11) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/TableOptions_Cell.md b/docs/reference/index/interfaces/TableOptions_Cell.md new file mode 100644 index 0000000000..6846124858 --- /dev/null +++ b/docs/reference/index/interfaces/TableOptions_Cell.md @@ -0,0 +1,24 @@ +--- +id: TableOptions_Cell +title: TableOptions_Cell +--- + +# Interface: TableOptions\_Cell + +Defined in: [core/cells/coreCellsFeature.types.ts:63](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L63) + +## Extended by + +- [`TableOptions_Core`](TableOptions_Core.md) + +## Properties + +### renderFallbackValue? + +```ts +optional renderFallbackValue: any; +``` + +Defined in: [core/cells/coreCellsFeature.types.ts:67](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L67) + +Value used when the desired value is not found in the data. diff --git a/docs/reference/index/interfaces/TableOptions_ColumnFiltering.md b/docs/reference/index/interfaces/TableOptions_ColumnFiltering.md new file mode 100644 index 0000000000..880589a701 --- /dev/null +++ b/docs/reference/index/interfaces/TableOptions_ColumnFiltering.md @@ -0,0 +1,94 @@ +--- +id: TableOptions_ColumnFiltering +title: TableOptions_ColumnFiltering +--- + +# Interface: TableOptions\_ColumnFiltering\ + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:143](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L143) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### enableColumnFilters? + +```ts +optional enableColumnFilters: boolean; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:150](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L150) + +Enables/disables **column** filtering for all columns. + +*** + +### enableFilters? + +```ts +optional enableFilters: boolean; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:154](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L154) + +Enables/disables all filtering for the table. + +*** + +### filterFromLeafRows? + +```ts +optional filterFromLeafRows: boolean; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:158](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L158) + +By default, filtering is done from parent rows down (so if a parent row is filtered out, all of its children will be filtered out as well). Setting this option to `true` will cause filtering to be done from leaf rows up (which means parent rows will be included so long as one of their child or grand-child rows is also included). + +*** + +### manualFiltering? + +```ts +optional manualFiltering: boolean; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:162](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L162) + +Disables the `getFilteredRowModel` from being used to filter data. This may be useful if your table needs to dynamically support both client-side and server-side filtering. + +*** + +### maxLeafRowFilterDepth? + +```ts +optional maxLeafRowFilterDepth: number; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:168](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L168) + +By default, filtering is done for all rows (max depth of 100), no matter if they are root level parent rows or the child leaf rows of a parent row. Setting this option to `0` will cause filtering to only be applied to the root level parent rows, with all sub-rows remaining unfiltered. Similarly, setting this option to `1` will cause filtering to only be applied to child leaf rows 1 level deep, and so on. + +This is useful for situations where you want a row's entire child hierarchy to be visible regardless of the applied filter. + +*** + +### onColumnFiltersChange? + +```ts +optional onColumnFiltersChange: OnChangeFn; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:174](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L174) + +Called with an updater when column filter state changes. Pair this with +`state.columnFilters` when using external state; external atoms can own the +slice without this callback. diff --git a/docs/reference/index/interfaces/TableOptions_ColumnGrouping.md b/docs/reference/index/interfaces/TableOptions_ColumnGrouping.md new file mode 100644 index 0000000000..f9d376d414 --- /dev/null +++ b/docs/reference/index/interfaces/TableOptions_ColumnGrouping.md @@ -0,0 +1,58 @@ +--- +id: TableOptions_ColumnGrouping +title: TableOptions_ColumnGrouping +--- + +# Interface: TableOptions\_ColumnGrouping + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:152](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L152) + +## Properties + +### enableGrouping? + +```ts +optional enableGrouping: boolean; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:156](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L156) + +Enables/disables grouping for the table. + +*** + +### groupedColumnMode? + +```ts +optional groupedColumnMode: false | "reorder" | "remove"; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:160](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L160) + +Grouping columns are automatically reordered by default to the start of the columns list. If you would rather remove them or leave them as-is, set the appropriate mode here. + +*** + +### manualGrouping? + +```ts +optional manualGrouping: boolean; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:164](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L164) + +Enables manual grouping. If this option is set to `true`, the table will not automatically group rows using `getGroupedRowModel()` and instead will expect you to manually group the rows before passing them to the table. This is useful if you are doing server-side grouping and aggregation. + +*** + +### onGroupingChange? + +```ts +optional onGroupingChange: OnChangeFn; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:170](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L170) + +Called with an updater when grouping state changes. Pair this with +`state.grouping` when using external state; external atoms can own the +slice without this callback. diff --git a/docs/reference/index/interfaces/TableOptions_ColumnOrdering.md b/docs/reference/index/interfaces/TableOptions_ColumnOrdering.md new file mode 100644 index 0000000000..8cf2723b72 --- /dev/null +++ b/docs/reference/index/interfaces/TableOptions_ColumnOrdering.md @@ -0,0 +1,22 @@ +--- +id: TableOptions_ColumnOrdering +title: TableOptions_ColumnOrdering +--- + +# Interface: TableOptions\_ColumnOrdering + +Defined in: [features/column-ordering/columnOrderingFeature.types.ts:11](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-ordering/columnOrderingFeature.types.ts#L11) + +## Properties + +### onColumnOrderChange? + +```ts +optional onColumnOrderChange: OnChangeFn; +``` + +Defined in: [features/column-ordering/columnOrderingFeature.types.ts:17](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-ordering/columnOrderingFeature.types.ts#L17) + +Called with an updater when column order state changes. Pair this with +`state.columnOrder` when using external state; external atoms can own the +slice without this callback. diff --git a/docs/reference/index/interfaces/TableOptions_ColumnPinning.md b/docs/reference/index/interfaces/TableOptions_ColumnPinning.md new file mode 100644 index 0000000000..52ddfb2d04 --- /dev/null +++ b/docs/reference/index/interfaces/TableOptions_ColumnPinning.md @@ -0,0 +1,34 @@ +--- +id: TableOptions_ColumnPinning +title: TableOptions_ColumnPinning +--- + +# Interface: TableOptions\_ColumnPinning + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:19](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L19) + +## Properties + +### enableColumnPinning? + +```ts +optional enableColumnPinning: boolean; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:23](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L23) + +Enables/disables column pinning for the table. Defaults to `true`. + +*** + +### onColumnPinningChange? + +```ts +optional onColumnPinningChange: OnChangeFn; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:29](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L29) + +Called with an updater when column pinning state changes. Pair this with +`state.columnPinning` when using external state; external atoms can own the +slice without this callback. diff --git a/docs/reference/index/interfaces/TableOptions_ColumnResizing.md b/docs/reference/index/interfaces/TableOptions_ColumnResizing.md new file mode 100644 index 0000000000..14294155de --- /dev/null +++ b/docs/reference/index/interfaces/TableOptions_ColumnResizing.md @@ -0,0 +1,60 @@ +--- +id: TableOptions_ColumnResizing +title: TableOptions_ColumnResizing +--- + +# Interface: TableOptions\_ColumnResizing + +Defined in: [features/column-resizing/columnResizingFeature.types.ts:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts#L20) + +## Properties + +### columnResizeDirection? + +```ts +optional columnResizeDirection: ColumnResizeDirection; +``` + +Defined in: [features/column-resizing/columnResizingFeature.types.ts:34](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts#L34) + +Sets the resize direction used to calculate drag offsets. Defaults to `ltr`. + +*** + +### columnResizeMode? + +```ts +optional columnResizeMode: ColumnResizeMode; +``` + +Defined in: [features/column-resizing/columnResizingFeature.types.ts:26](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts#L26) + +Determines when committed `columnSizing` values are updated. `onChange` +commits sizes while the resize handle is dragged; `onEnd` commits when the +resize interaction finishes. + +*** + +### enableColumnResizing? + +```ts +optional enableColumnResizing: boolean; +``` + +Defined in: [features/column-resizing/columnResizingFeature.types.ts:30](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts#L30) + +Enables or disables column resizing for the whole table. + +*** + +### onColumnResizingChange? + +```ts +optional onColumnResizingChange: OnChangeFn; +``` + +Defined in: [features/column-resizing/columnResizingFeature.types.ts:40](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts#L40) + +Called with an updater when the transient `columnResizing` state changes. +Pair this with `state.columnResizing` when using external state; external +atoms can own the slice without this callback. diff --git a/docs/reference/index/interfaces/TableOptions_ColumnSizing.md b/docs/reference/index/interfaces/TableOptions_ColumnSizing.md new file mode 100644 index 0000000000..93ac863686 --- /dev/null +++ b/docs/reference/index/interfaces/TableOptions_ColumnSizing.md @@ -0,0 +1,22 @@ +--- +id: TableOptions_ColumnSizing +title: TableOptions_ColumnSizing +--- + +# Interface: TableOptions\_ColumnSizing + +Defined in: [features/column-sizing/columnSizingFeature.types.ts:10](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.types.ts#L10) + +## Properties + +### onColumnSizingChange? + +```ts +optional onColumnSizingChange: OnChangeFn; +``` + +Defined in: [features/column-sizing/columnSizingFeature.types.ts:16](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.types.ts#L16) + +Called with an updater when committed column sizing state changes. Pair +this with `state.columnSizing` when using external state; external atoms +can own the slice without this callback. diff --git a/docs/reference/index/interfaces/TableOptions_ColumnVisibility.md b/docs/reference/index/interfaces/TableOptions_ColumnVisibility.md new file mode 100644 index 0000000000..f1991ac0d6 --- /dev/null +++ b/docs/reference/index/interfaces/TableOptions_ColumnVisibility.md @@ -0,0 +1,34 @@ +--- +id: TableOptions_ColumnVisibility +title: TableOptions_ColumnVisibility +--- + +# Interface: TableOptions\_ColumnVisibility + +Defined in: [features/column-visibility/columnVisibilityFeature.types.ts:12](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.types.ts#L12) + +## Properties + +### enableHiding? + +```ts +optional enableHiding: boolean; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.types.ts:16](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.types.ts#L16) + +Whether to enable column hiding. Defaults to `true`. + +*** + +### onColumnVisibilityChange? + +```ts +optional onColumnVisibilityChange: OnChangeFn; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.types.ts:22](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.types.ts#L22) + +Called with an updater when column visibility state changes. Pair this with +`state.columnVisibility` when using external state; external atoms can own +the slice without this callback. diff --git a/docs/reference/index/interfaces/TableOptions_Columns.md b/docs/reference/index/interfaces/TableOptions_Columns.md new file mode 100644 index 0000000000..45ae5215df --- /dev/null +++ b/docs/reference/index/interfaces/TableOptions_Columns.md @@ -0,0 +1,50 @@ +--- +id: TableOptions_Columns +title: TableOptions_Columns +--- + +# Interface: TableOptions\_Columns\ + +Defined in: [core/columns/coreColumnsFeature.types.ts:60](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L60) + +## Extended by + +- [`TableOptions_Core`](TableOptions_Core.md) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](../type-aliases/CellData.md) = [`CellData`](../type-aliases/CellData.md) + +## Properties + +### columns + +```ts +columns: readonly ColumnDef[]; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:68](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L68) + +The array of column defs to use for the table. + +*** + +### defaultColumn? + +```ts +optional defaultColumn: Partial>; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:72](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L72) + +Default column options to use for all column defs supplied to the table. diff --git a/docs/reference/index/interfaces/TableOptions_Core.md b/docs/reference/index/interfaces/TableOptions_Core.md new file mode 100644 index 0000000000..cae823f1c9 --- /dev/null +++ b/docs/reference/index/interfaces/TableOptions_Core.md @@ -0,0 +1,315 @@ +--- +id: TableOptions_Core +title: TableOptions_Core +--- + +# Interface: TableOptions\_Core\ + +Defined in: [types/TableOptions.ts:27](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L27) + +## Extends + +- [`TableOptions_Table`](TableOptions_Table.md)\<`TFeatures`, `TData`\>.[`TableOptions_Cell`](TableOptions_Cell.md).[`TableOptions_Columns`](TableOptions_Columns.md)\<`TFeatures`, `TData`\>.[`TableOptions_Rows`](TableOptions_Rows.md)\<`TFeatures`, `TData`\> + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### \_features + +```ts +readonly _features: TFeatures; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:71](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L71) + +The features that you want to enable for the table. + +#### Inherited from + +[`TableOptions_Table`](TableOptions_Table.md).[`_features`](TableOptions_Table.md#_features) + +*** + +### \_rowModels? + +```ts +readonly optional _rowModels: CreateRowModels_All; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:75](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L75) + +The row model options that you want to enable for the table. + +#### Inherited from + +[`TableOptions_Table`](TableOptions_Table.md).[`_rowModels`](TableOptions_Table.md#_rowmodels) + +*** + +### atoms? + +```ts +readonly optional atoms: Partial<{ [K in string | number | symbol]: Atom[K]> }>; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:83](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L83) + +Optionally, provide your own external writable atoms for individual state slices. +When an atom is provided for a given slice, it takes precedence over `options.state[key]` +and the internal base atom for that slice. Feature state update APIs write through +the corresponding atom updater, so external atoms are the preferred v9 ownership +model for app-managed table state slices. + +#### Inherited from + +[`TableOptions_Table`](TableOptions_Table.md).[`atoms`](TableOptions_Table.md#atoms) + +*** + +### autoResetAll? + +```ts +readonly optional autoResetAll: boolean; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:87](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L87) + +Set this option to override any of the `autoReset...` feature options. + +#### Inherited from + +[`TableOptions_Table`](TableOptions_Table.md).[`autoResetAll`](TableOptions_Table.md#autoresetall) + +*** + +### columns + +```ts +columns: readonly ColumnDef[]; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:68](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L68) + +The array of column defs to use for the table. + +#### Inherited from + +[`TableOptions_Columns`](TableOptions_Columns.md).[`columns`](TableOptions_Columns.md#columns) + +*** + +### data + +```ts +readonly data: readonly TData[]; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:91](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L91) + +The data for the table to display. When the `data` option changes reference, the table will reprocess the data. + +#### Inherited from + +[`TableOptions_Table`](TableOptions_Table.md).[`data`](TableOptions_Table.md#data) + +*** + +### defaultColumn? + +```ts +optional defaultColumn: Partial>; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:72](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L72) + +Default column options to use for all column defs supplied to the table. + +#### Inherited from + +[`TableOptions_Columns`](TableOptions_Columns.md).[`defaultColumn`](TableOptions_Columns.md#defaultcolumn) + +*** + +### getRowId()? + +```ts +optional getRowId: (originalRow, index, parent?) => string; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:90](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L90) + +This optional function is used to derive a unique ID for any given row. If not provided the rows index is used (nested rows join together with `.` using their grandparents' index eg. `index.index.index`). If you need to identify individual rows that are originating from any server-side operations, it's suggested you use this function to return an ID that makes sense regardless of network IO/ambiguity eg. a userId, taskId, database ID field, etc. + +#### Parameters + +##### originalRow + +`TData` + +##### index + +`number` + +##### parent? + +[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\> + +#### Returns + +`string` + +#### Example + +```ts +getRowId: row => row.userId +``` + +#### Inherited from + +[`TableOptions_Rows`](TableOptions_Rows.md).[`getRowId`](TableOptions_Rows.md#getrowid) + +*** + +### getSubRows()? + +```ts +optional getSubRows: (originalRow, index) => readonly TData[] | undefined; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:99](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L99) + +This optional function is used to access the sub rows for any given row. If you are using nested rows, you will need to use this function to return the sub rows object (or undefined) from the row. + +#### Parameters + +##### originalRow + +`TData` + +##### index + +`number` + +#### Returns + +readonly `TData`[] \| `undefined` + +#### Example + +```ts +getSubRows: row => row.subRows +``` + +#### Inherited from + +[`TableOptions_Rows`](TableOptions_Rows.md).[`getSubRows`](TableOptions_Rows.md#getsubrows) + +*** + +### initialState? + +```ts +readonly optional initialState: Partial>; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:98](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L98) + +Optionally provide starting values for registered table state slices. +Feature reset APIs use this value by default, and many reset APIs accept +`true` to reset to that feature's blank/default state instead. Changing this +object later does not reset table state, so it does not need to be stable. + +#### Inherited from + +[`TableOptions_Table`](TableOptions_Table.md).[`initialState`](TableOptions_Table.md#initialstate) + +*** + +### mergeOptions()? + +```ts +readonly optional mergeOptions: (defaultOptions, options) => TableOptions; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:102](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L102) + +This option is used to optionally implement the merging of table options. + +#### Parameters + +##### defaultOptions + +[`TableOptions`](../type-aliases/TableOptions.md)\<`TFeatures`, `TData`\> + +##### options + +`Partial`\<[`TableOptions`](../type-aliases/TableOptions.md)\<`TFeatures`, `TData`\>\> + +#### Returns + +[`TableOptions`](../type-aliases/TableOptions.md)\<`TFeatures`, `TData`\> + +#### Inherited from + +[`TableOptions_Table`](TableOptions_Table.md).[`mergeOptions`](TableOptions_Table.md#mergeoptions) + +*** + +### meta? + +```ts +readonly optional meta: TableMeta; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:109](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L109) + +You can pass any object to `options.meta` and access it anywhere the `table` is available via `table.options.meta`. + +#### Inherited from + +[`TableOptions_Table`](TableOptions_Table.md).[`meta`](TableOptions_Table.md#meta) + +*** + +### renderFallbackValue? + +```ts +optional renderFallbackValue: any; +``` + +Defined in: [core/cells/coreCellsFeature.types.ts:67](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.types.ts#L67) + +Value used when the desired value is not found in the data. + +#### Inherited from + +[`TableOptions_Cell`](TableOptions_Cell.md).[`renderFallbackValue`](TableOptions_Cell.md#renderfallbackvalue) + +*** + +### state? + +```ts +readonly optional state: Partial>; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:117](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L117) + +Optionally provide externally managed values for individual state slices. + +Pair each slice with its matching `on[State]Change` callback so table state +updates can be persisted outside the table. External atoms take precedence +over this option when both are provided for the same slice. + +#### Inherited from + +[`TableOptions_Table`](TableOptions_Table.md).[`state`](TableOptions_Table.md#state) diff --git a/docs/reference/index/interfaces/TableOptions_GlobalFiltering.md b/docs/reference/index/interfaces/TableOptions_GlobalFiltering.md new file mode 100644 index 0000000000..48941b498e --- /dev/null +++ b/docs/reference/index/interfaces/TableOptions_GlobalFiltering.md @@ -0,0 +1,96 @@ +--- +id: TableOptions_GlobalFiltering +title: TableOptions_GlobalFiltering +--- + +# Interface: TableOptions\_GlobalFiltering\ + +Defined in: [features/global-filtering/globalFilteringFeature.types.ts:32](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.types.ts#L32) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### enableGlobalFilter? + +```ts +optional enableGlobalFilter: boolean; +``` + +Defined in: [features/global-filtering/globalFilteringFeature.types.ts:39](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.types.ts#L39) + +Enables/disables **global** filtering for all columns. + +*** + +### getColumnCanGlobalFilter()? + +```ts +optional getColumnCanGlobalFilter: (column) => boolean; +``` + +Defined in: [features/global-filtering/globalFilteringFeature.types.ts:44](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.types.ts#L44) + +If provided, this function will be called with the column and should return `true` or `false` to indicate whether this column should be used for global filtering. +This is useful if the column can contain data that is not `string` or `number` (i.e. `undefined`). + +#### Type Parameters + +##### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +##### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +##### TValue + +`TValue` *extends* `unknown` = `unknown` + +#### Parameters + +##### column + +[`Column`](../type-aliases/Column.md)\<`TFeatures`, `TData`, `TValue`\> + +#### Returns + +`boolean` + +*** + +### globalFilterFn? + +```ts +optional globalFilterFn: FilterFnOption; +``` + +Defined in: [features/global-filtering/globalFilteringFeature.types.ts:57](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.types.ts#L57) + +The filter function to use for global filtering. +- A `string` referencing a built-in filter function +- A `string` that references a custom filter functions provided via the `tableOptions.filterFns` option +- A custom filter function + +*** + +### onGlobalFilterChange? + +```ts +optional onGlobalFilterChange: OnChangeFn; +``` + +Defined in: [features/global-filtering/globalFilteringFeature.types.ts:63](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.types.ts#L63) + +Called with an updater when global filter state changes. Pair this with +`state.globalFilter` when using external state; external atoms can own the +slice without this callback. diff --git a/docs/reference/index/interfaces/TableOptions_Plugins.md b/docs/reference/index/interfaces/TableOptions_Plugins.md new file mode 100644 index 0000000000..a5efa5289b --- /dev/null +++ b/docs/reference/index/interfaces/TableOptions_Plugins.md @@ -0,0 +1,18 @@ +--- +id: TableOptions_Plugins +title: TableOptions_Plugins +--- + +# Interface: TableOptions\_Plugins\ + +Defined in: [types/TableOptions.ts:22](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L22) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/TableOptions_RowExpanding.md b/docs/reference/index/interfaces/TableOptions_RowExpanding.md new file mode 100644 index 0000000000..94b41cd6d3 --- /dev/null +++ b/docs/reference/index/interfaces/TableOptions_RowExpanding.md @@ -0,0 +1,124 @@ +--- +id: TableOptions_RowExpanding +title: TableOptions_RowExpanding +--- + +# Interface: TableOptions\_RowExpanding\ + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:37](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L37) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### autoResetExpanded? + +```ts +optional autoResetExpanded: boolean; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:44](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L44) + +Enables automatic expanded-state resets when page-altering table state changes. + +*** + +### enableExpanding? + +```ts +optional enableExpanding: boolean; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:48](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L48) + +Enable/disable expanding for all rows. + +*** + +### getIsRowExpanded()? + +```ts +optional getIsRowExpanded: (row) => boolean; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:52](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L52) + +If provided, allows you to override the default behavior of determining whether a row is currently expanded. + +#### Parameters + +##### row + +[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\> + +#### Returns + +`boolean` + +*** + +### getRowCanExpand()? + +```ts +optional getRowCanExpand: (row) => boolean; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:56](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L56) + +If provided, allows you to override the default behavior of determining whether a row can be expanded. + +#### Parameters + +##### row + +[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\> + +#### Returns + +`boolean` + +*** + +### manualExpanding? + +```ts +optional manualExpanding: boolean; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:60](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L60) + +Enables manual row expansion. If this is set to `true`, `getExpandedRowModel` will not be used to expand rows and you would be expected to perform the expansion in your own data model. This is useful if you are doing server-side expansion. + +*** + +### onExpandedChange? + +```ts +optional onExpandedChange: OnChangeFn; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:66](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L66) + +Called with an updater when expanded state changes. Pair this with +`state.expanded` when using external state; external atoms can own the +slice without this callback. + +*** + +### paginateExpandedRows? + +```ts +optional paginateExpandedRows: boolean; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:70](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L70) + +If `true` expanded rows will be paginated along with the rest of the table (which means expanded rows may span multiple pages). If `false` expanded rows will not be considered for pagination (which means expanded rows will always render on their parents page. This also means more rows will be rendered than the set page size) diff --git a/docs/reference/index/interfaces/TableOptions_RowPagination.md b/docs/reference/index/interfaces/TableOptions_RowPagination.md new file mode 100644 index 0000000000..d3684c3f85 --- /dev/null +++ b/docs/reference/index/interfaces/TableOptions_RowPagination.md @@ -0,0 +1,70 @@ +--- +id: TableOptions_RowPagination +title: TableOptions_RowPagination +--- + +# Interface: TableOptions\_RowPagination + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:15](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L15) + +## Properties + +### autoResetPageIndex? + +```ts +optional autoResetPageIndex: boolean; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:19](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L19) + +If set to `true`, pagination will be reset to the first page when page-altering state changes eg. `data` is updated, filters change, grouping changes, etc. + +*** + +### manualPagination? + +```ts +optional manualPagination: boolean; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:23](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L23) + +Enables manual pagination. If this option is set to `true`, the table will not automatically paginate rows using `getPaginatedRowModel()` and instead will expect you to manually paginate the rows before passing them to the table. This is useful if you are doing server-side pagination and aggregation. + +*** + +### onPaginationChange? + +```ts +optional onPaginationChange: OnChangeFn; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:29](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L29) + +Called with an updater when pagination state changes. Pair this with +`state.pagination` when using external state; external atoms can own the +slice without this callback. + +*** + +### pageCount? + +```ts +optional pageCount: number; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:33](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L33) + +When manually controlling pagination, you can supply a total `pageCount` value to the table if you know it (Or supply a `rowCount` and `pageCount` will be calculated). If you do not know how many pages there are, you can set this to `-1`. + +*** + +### rowCount? + +```ts +optional rowCount: number; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:37](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L37) + +When manually controlling pagination, you can supply a total `rowCount` value to the table if you know it. The `pageCount` can be calculated from this value and the `pageSize`. diff --git a/docs/reference/index/interfaces/TableOptions_RowPinning.md b/docs/reference/index/interfaces/TableOptions_RowPinning.md new file mode 100644 index 0000000000..b45fa7caa7 --- /dev/null +++ b/docs/reference/index/interfaces/TableOptions_RowPinning.md @@ -0,0 +1,56 @@ +--- +id: TableOptions_RowPinning +title: TableOptions_RowPinning +--- + +# Interface: TableOptions\_RowPinning\ + +Defined in: [features/row-pinning/rowPinningFeature.types.ts:16](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.types.ts#L16) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### enableRowPinning? + +```ts +optional enableRowPinning: boolean | (row) => boolean; +``` + +Defined in: [features/row-pinning/rowPinningFeature.types.ts:23](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.types.ts#L23) + +Enables/disables row pinning for the table. Defaults to `true`. + +*** + +### keepPinnedRows? + +```ts +optional keepPinnedRows: boolean; +``` + +Defined in: [features/row-pinning/rowPinningFeature.types.ts:27](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.types.ts#L27) + +When `false`, pinned rows will not be visible if they are filtered or paginated out of the table. When `true`, pinned rows will always be visible regardless of filtering or pagination. Defaults to `true`. + +*** + +### onRowPinningChange? + +```ts +optional onRowPinningChange: OnChangeFn; +``` + +Defined in: [features/row-pinning/rowPinningFeature.types.ts:33](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.types.ts#L33) + +Called with an updater when row pinning state changes. Pair this with +`state.rowPinning` when using external state; external atoms can own the +slice without this callback. diff --git a/docs/reference/index/interfaces/TableOptions_RowSelection.md b/docs/reference/index/interfaces/TableOptions_RowSelection.md new file mode 100644 index 0000000000..2ee9a181f4 --- /dev/null +++ b/docs/reference/index/interfaces/TableOptions_RowSelection.md @@ -0,0 +1,71 @@ +--- +id: TableOptions_RowSelection +title: TableOptions_RowSelection +--- + +# Interface: TableOptions\_RowSelection\ + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:12](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L12) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### enableMultiRowSelection? + +```ts +optional enableMultiRowSelection: boolean | (row) => boolean; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L20) + +- Enables/disables multiple row selection for all rows in the table OR +- A function that given a row, returns whether to enable/disable multiple row selection for that row's children/grandchildren + +*** + +### enableRowSelection? + +```ts +optional enableRowSelection: boolean | (row) => boolean; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:25](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L25) + +- Enables/disables row selection for all rows in the table OR +- A function that given a row, returns whether to enable/disable row selection for that row + +*** + +### enableSubRowSelection? + +```ts +optional enableSubRowSelection: boolean | (row) => boolean; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:30](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L30) + +Enables/disables automatic sub-row selection when a parent row is selected, or a function that enables/disables automatic sub-row selection for each row. +(Use in combination with expanding or grouping features) + +*** + +### onRowSelectionChange? + +```ts +optional onRowSelectionChange: OnChangeFn; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:36](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L36) + +Called with an updater when row selection state changes. Pair this with +`state.rowSelection` when using external state; external atoms can own the +slice without this callback. diff --git a/docs/reference/index/interfaces/TableOptions_RowSorting.md b/docs/reference/index/interfaces/TableOptions_RowSorting.md new file mode 100644 index 0000000000..84900e14c9 --- /dev/null +++ b/docs/reference/index/interfaces/TableOptions_RowSorting.md @@ -0,0 +1,130 @@ +--- +id: TableOptions_RowSorting +title: TableOptions_RowSorting +--- + +# Interface: TableOptions\_RowSorting + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:143](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L143) + +## Properties + +### enableMultiRemove? + +```ts +optional enableMultiRemove: boolean; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:147](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L147) + +Enables/disables the ability to remove multi-sorts + +*** + +### enableMultiSort? + +```ts +optional enableMultiSort: boolean; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:151](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L151) + +Enables/Disables multi-sorting for the table. + +*** + +### enableSorting? + +```ts +optional enableSorting: boolean; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:155](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L155) + +Enables/Disables sorting for the table. + +*** + +### enableSortingRemoval? + +```ts +optional enableSortingRemoval: boolean; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:161](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L161) + +Enables/Disables the ability to remove sorting for the table. +- If `true` then changing sort order will circle like: 'none' -> 'desc' -> 'asc' -> 'none' -> ... +- If `false` then changing sort order will circle like: 'none' -> 'desc' -> 'asc' -> 'desc' -> 'asc' -> ... + +*** + +### isMultiSortEvent()? + +```ts +optional isMultiSortEvent: (e) => boolean; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:165](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L165) + +Pass a custom function that will be used to determine if a multi-sort event should be triggered. It is passed the event from the sort toggle handler and should return `true` if the event should trigger a multi-sort. + +#### Parameters + +##### e + +`unknown` + +#### Returns + +`boolean` + +*** + +### manualSorting? + +```ts +optional manualSorting: boolean; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:169](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L169) + +Enables manual sorting for the table. If this is `true`, you will be expected to sort your data before it is passed to the table. This is useful if you are doing server-side sorting. + +*** + +### maxMultiSortColCount? + +```ts +optional maxMultiSortColCount: number; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:173](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L173) + +Set a maximum number of columns that can be multi-sorted. + +*** + +### onSortingChange? + +```ts +optional onSortingChange: OnChangeFn; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:179](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L179) + +Called with an updater when sorting state changes. Pair this with +`state.sorting` when using external state; external atoms can own the slice +without this callback. + +*** + +### sortDescFirst? + +```ts +optional sortDescFirst: boolean; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:183](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L183) + +If `true`, all sorts will default to descending as their first toggle state. diff --git a/docs/reference/index/interfaces/TableOptions_Rows.md b/docs/reference/index/interfaces/TableOptions_Rows.md new file mode 100644 index 0000000000..232a3fa0d1 --- /dev/null +++ b/docs/reference/index/interfaces/TableOptions_Rows.md @@ -0,0 +1,90 @@ +--- +id: TableOptions_Rows +title: TableOptions_Rows +--- + +# Interface: TableOptions\_Rows\ + +Defined in: [core/rows/coreRowsFeature.types.ts:82](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L82) + +## Extended by + +- [`TableOptions_Core`](TableOptions_Core.md) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### getRowId()? + +```ts +optional getRowId: (originalRow, index, parent?) => string; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:90](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L90) + +This optional function is used to derive a unique ID for any given row. If not provided the rows index is used (nested rows join together with `.` using their grandparents' index eg. `index.index.index`). If you need to identify individual rows that are originating from any server-side operations, it's suggested you use this function to return an ID that makes sense regardless of network IO/ambiguity eg. a userId, taskId, database ID field, etc. + +#### Parameters + +##### originalRow + +`TData` + +##### index + +`number` + +##### parent? + +[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\> + +#### Returns + +`string` + +#### Example + +```ts +getRowId: row => row.userId +``` + +*** + +### getSubRows()? + +```ts +optional getSubRows: (originalRow, index) => readonly TData[] | undefined; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:99](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L99) + +This optional function is used to access the sub rows for any given row. If you are using nested rows, you will need to use this function to return the sub rows object (or undefined) from the row. + +#### Parameters + +##### originalRow + +`TData` + +##### index + +`number` + +#### Returns + +readonly `TData`[] \| `undefined` + +#### Example + +```ts +getSubRows: row => row.subRows +``` diff --git a/docs/reference/index/interfaces/TableOptions_Table.md b/docs/reference/index/interfaces/TableOptions_Table.md new file mode 100644 index 0000000000..5fb714722c --- /dev/null +++ b/docs/reference/index/interfaces/TableOptions_Table.md @@ -0,0 +1,155 @@ +--- +id: TableOptions_Table +title: TableOptions_Table +--- + +# Interface: TableOptions\_Table\ + +Defined in: [core/table/coreTablesFeature.types.ts:64](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L64) + +## Extended by + +- [`TableOptions_Core`](TableOptions_Core.md) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### \_features + +```ts +readonly _features: TFeatures; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:71](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L71) + +The features that you want to enable for the table. + +*** + +### \_rowModels? + +```ts +readonly optional _rowModels: CreateRowModels_All; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:75](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L75) + +The row model options that you want to enable for the table. + +*** + +### atoms? + +```ts +readonly optional atoms: Partial<{ [K in string | number | symbol]: Atom[K]> }>; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:83](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L83) + +Optionally, provide your own external writable atoms for individual state slices. +When an atom is provided for a given slice, it takes precedence over `options.state[key]` +and the internal base atom for that slice. Feature state update APIs write through +the corresponding atom updater, so external atoms are the preferred v9 ownership +model for app-managed table state slices. + +*** + +### autoResetAll? + +```ts +readonly optional autoResetAll: boolean; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:87](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L87) + +Set this option to override any of the `autoReset...` feature options. + +*** + +### data + +```ts +readonly data: readonly TData[]; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:91](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L91) + +The data for the table to display. When the `data` option changes reference, the table will reprocess the data. + +*** + +### initialState? + +```ts +readonly optional initialState: Partial>; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:98](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L98) + +Optionally provide starting values for registered table state slices. +Feature reset APIs use this value by default, and many reset APIs accept +`true` to reset to that feature's blank/default state instead. Changing this +object later does not reset table state, so it does not need to be stable. + +*** + +### mergeOptions()? + +```ts +readonly optional mergeOptions: (defaultOptions, options) => TableOptions; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:102](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L102) + +This option is used to optionally implement the merging of table options. + +#### Parameters + +##### defaultOptions + +[`TableOptions`](../type-aliases/TableOptions.md)\<`TFeatures`, `TData`\> + +##### options + +`Partial`\<[`TableOptions`](../type-aliases/TableOptions.md)\<`TFeatures`, `TData`\>\> + +#### Returns + +[`TableOptions`](../type-aliases/TableOptions.md)\<`TFeatures`, `TData`\> + +*** + +### meta? + +```ts +readonly optional meta: TableMeta; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:109](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L109) + +You can pass any object to `options.meta` and access it anywhere the `table` is available via `table.options.meta`. + +*** + +### state? + +```ts +readonly optional state: Partial>; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:117](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L117) + +Optionally provide externally managed values for individual state slices. + +Pair each slice with its matching `on[State]Change` callback so table state +updates can be persisted outside the table. External atoms take precedence +over this option when both are provided for the same slice. diff --git a/docs/reference/index/interfaces/TableState_ColumnFiltering.md b/docs/reference/index/interfaces/TableState_ColumnFiltering.md new file mode 100644 index 0000000000..16e4146660 --- /dev/null +++ b/docs/reference/index/interfaces/TableState_ColumnFiltering.md @@ -0,0 +1,18 @@ +--- +id: TableState_ColumnFiltering +title: TableState_ColumnFiltering +--- + +# Interface: TableState\_ColumnFiltering + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:18](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L18) + +## Properties + +### columnFilters + +```ts +columnFilters: ColumnFiltersState; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:19](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L19) diff --git a/docs/reference/index/interfaces/TableState_ColumnGrouping.md b/docs/reference/index/interfaces/TableState_ColumnGrouping.md new file mode 100644 index 0000000000..ab7605d5ef --- /dev/null +++ b/docs/reference/index/interfaces/TableState_ColumnGrouping.md @@ -0,0 +1,18 @@ +--- +id: TableState_ColumnGrouping +title: TableState_ColumnGrouping +--- + +# Interface: TableState\_ColumnGrouping + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:17](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L17) + +## Properties + +### grouping + +```ts +grouping: GroupingState; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:18](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L18) diff --git a/docs/reference/index/interfaces/TableState_ColumnOrdering.md b/docs/reference/index/interfaces/TableState_ColumnOrdering.md new file mode 100644 index 0000000000..805e587675 --- /dev/null +++ b/docs/reference/index/interfaces/TableState_ColumnOrdering.md @@ -0,0 +1,18 @@ +--- +id: TableState_ColumnOrdering +title: TableState_ColumnOrdering +--- + +# Interface: TableState\_ColumnOrdering + +Defined in: [features/column-ordering/columnOrderingFeature.types.ts:7](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-ordering/columnOrderingFeature.types.ts#L7) + +## Properties + +### columnOrder + +```ts +columnOrder: ColumnOrderState; +``` + +Defined in: [features/column-ordering/columnOrderingFeature.types.ts:8](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-ordering/columnOrderingFeature.types.ts#L8) diff --git a/docs/reference/index/interfaces/TableState_ColumnPinning.md b/docs/reference/index/interfaces/TableState_ColumnPinning.md new file mode 100644 index 0000000000..305207947d --- /dev/null +++ b/docs/reference/index/interfaces/TableState_ColumnPinning.md @@ -0,0 +1,18 @@ +--- +id: TableState_ColumnPinning +title: TableState_ColumnPinning +--- + +# Interface: TableState\_ColumnPinning + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:15](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L15) + +## Properties + +### columnPinning + +```ts +columnPinning: ColumnPinningState; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:16](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L16) diff --git a/docs/reference/index/interfaces/TableState_ColumnResizing.md b/docs/reference/index/interfaces/TableState_ColumnResizing.md new file mode 100644 index 0000000000..3a5119ade5 --- /dev/null +++ b/docs/reference/index/interfaces/TableState_ColumnResizing.md @@ -0,0 +1,18 @@ +--- +id: TableState_ColumnResizing +title: TableState_ColumnResizing +--- + +# Interface: TableState\_ColumnResizing + +Defined in: [features/column-resizing/columnResizingFeature.types.ts:3](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts#L3) + +## Properties + +### columnResizing + +```ts +columnResizing: columnResizingState; +``` + +Defined in: [features/column-resizing/columnResizingFeature.types.ts:4](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts#L4) diff --git a/docs/reference/index/interfaces/TableState_ColumnSizing.md b/docs/reference/index/interfaces/TableState_ColumnSizing.md new file mode 100644 index 0000000000..ec1ca1803a --- /dev/null +++ b/docs/reference/index/interfaces/TableState_ColumnSizing.md @@ -0,0 +1,18 @@ +--- +id: TableState_ColumnSizing +title: TableState_ColumnSizing +--- + +# Interface: TableState\_ColumnSizing + +Defined in: [features/column-sizing/columnSizingFeature.types.ts:4](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.types.ts#L4) + +## Properties + +### columnSizing + +```ts +columnSizing: ColumnSizingState; +``` + +Defined in: [features/column-sizing/columnSizingFeature.types.ts:5](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.types.ts#L5) diff --git a/docs/reference/index/interfaces/TableState_ColumnVisibility.md b/docs/reference/index/interfaces/TableState_ColumnVisibility.md new file mode 100644 index 0000000000..398c53102c --- /dev/null +++ b/docs/reference/index/interfaces/TableState_ColumnVisibility.md @@ -0,0 +1,18 @@ +--- +id: TableState_ColumnVisibility +title: TableState_ColumnVisibility +--- + +# Interface: TableState\_ColumnVisibility + +Defined in: [features/column-visibility/columnVisibilityFeature.types.ts:8](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.types.ts#L8) + +## Properties + +### columnVisibility + +```ts +columnVisibility: ColumnVisibilityState; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.types.ts:9](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.types.ts#L9) diff --git a/docs/reference/index/interfaces/TableState_GlobalFiltering.md b/docs/reference/index/interfaces/TableState_GlobalFiltering.md new file mode 100644 index 0000000000..743bd69f0c --- /dev/null +++ b/docs/reference/index/interfaces/TableState_GlobalFiltering.md @@ -0,0 +1,18 @@ +--- +id: TableState_GlobalFiltering +title: TableState_GlobalFiltering +--- + +# Interface: TableState\_GlobalFiltering + +Defined in: [features/global-filtering/globalFilteringFeature.types.ts:14](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.types.ts#L14) + +## Properties + +### globalFilter + +```ts +globalFilter: any; +``` + +Defined in: [features/global-filtering/globalFilteringFeature.types.ts:15](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.types.ts#L15) diff --git a/docs/reference/index/interfaces/TableState_Plugins.md b/docs/reference/index/interfaces/TableState_Plugins.md new file mode 100644 index 0000000000..ba298669ee --- /dev/null +++ b/docs/reference/index/interfaces/TableState_Plugins.md @@ -0,0 +1,17 @@ +--- +id: TableState_Plugins +title: TableState_Plugins +--- + +# Interface: TableState\_Plugins\ + +Defined in: [types/TableState.ts:21](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L21) + +Use this interface as a target for declaration merging to add your own state properties. +Note: This will affect the types of all tables in your project. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) diff --git a/docs/reference/index/interfaces/TableState_RowExpanding.md b/docs/reference/index/interfaces/TableState_RowExpanding.md new file mode 100644 index 0000000000..f644139305 --- /dev/null +++ b/docs/reference/index/interfaces/TableState_RowExpanding.md @@ -0,0 +1,18 @@ +--- +id: TableState_RowExpanding +title: TableState_RowExpanding +--- + +# Interface: TableState\_RowExpanding + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:10](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L10) + +## Properties + +### expanded + +```ts +expanded: ExpandedState; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:11](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L11) diff --git a/docs/reference/index/interfaces/TableState_RowPagination.md b/docs/reference/index/interfaces/TableState_RowPagination.md new file mode 100644 index 0000000000..d7581a58e8 --- /dev/null +++ b/docs/reference/index/interfaces/TableState_RowPagination.md @@ -0,0 +1,18 @@ +--- +id: TableState_RowPagination +title: TableState_RowPagination +--- + +# Interface: TableState\_RowPagination + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:11](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L11) + +## Properties + +### pagination + +```ts +pagination: PaginationState; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:12](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L12) diff --git a/docs/reference/index/interfaces/TableState_RowPinning.md b/docs/reference/index/interfaces/TableState_RowPinning.md new file mode 100644 index 0000000000..5a88ccec43 --- /dev/null +++ b/docs/reference/index/interfaces/TableState_RowPinning.md @@ -0,0 +1,18 @@ +--- +id: TableState_RowPinning +title: TableState_RowPinning +--- + +# Interface: TableState\_RowPinning + +Defined in: [features/row-pinning/rowPinningFeature.types.ts:12](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.types.ts#L12) + +## Properties + +### rowPinning + +```ts +rowPinning: RowPinningState; +``` + +Defined in: [features/row-pinning/rowPinningFeature.types.ts:13](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.types.ts#L13) diff --git a/docs/reference/index/interfaces/TableState_RowSelection.md b/docs/reference/index/interfaces/TableState_RowSelection.md new file mode 100644 index 0000000000..eb3ea16b29 --- /dev/null +++ b/docs/reference/index/interfaces/TableState_RowSelection.md @@ -0,0 +1,18 @@ +--- +id: TableState_RowSelection +title: TableState_RowSelection +--- + +# Interface: TableState\_RowSelection + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:8](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L8) + +## Properties + +### rowSelection + +```ts +rowSelection: RowSelectionState; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:9](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L9) diff --git a/docs/reference/index/interfaces/TableState_RowSorting.md b/docs/reference/index/interfaces/TableState_RowSorting.md new file mode 100644 index 0000000000..252d9a7611 --- /dev/null +++ b/docs/reference/index/interfaces/TableState_RowSorting.md @@ -0,0 +1,18 @@ +--- +id: TableState_RowSorting +title: TableState_RowSorting +--- + +# Interface: TableState\_RowSorting + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:17](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L17) + +## Properties + +### sorting + +```ts +sorting: SortingState; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:18](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L18) diff --git a/docs/reference/index/interfaces/Table_ColumnFaceting.md b/docs/reference/index/interfaces/Table_ColumnFaceting.md new file mode 100644 index 0000000000..d89195338d --- /dev/null +++ b/docs/reference/index/interfaces/Table_ColumnFaceting.md @@ -0,0 +1,66 @@ +--- +id: Table_ColumnFaceting +title: Table_ColumnFaceting +--- + +# Interface: Table\_ColumnFaceting\ + +Defined in: [features/column-faceting/columnFacetingFeature.types.ts:87](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.types.ts#L87) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### getGlobalFacetedMinMaxValues() + +```ts +getGlobalFacetedMinMaxValues: () => [number, number] | undefined; +``` + +Defined in: [features/column-faceting/columnFacetingFeature.types.ts:94](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.types.ts#L94) + +Returns the min and max values for the global filter. + +#### Returns + +\[`number`, `number`\] \| `undefined` + +*** + +### getGlobalFacetedRowModel() + +```ts +getGlobalFacetedRowModel: () => RowModel; +``` + +Defined in: [features/column-faceting/columnFacetingFeature.types.ts:98](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.types.ts#L98) + +Returns the row model for the table after **global** filtering has been applied. + +#### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> + +*** + +### getGlobalFacetedUniqueValues() + +```ts +getGlobalFacetedUniqueValues: () => Map; +``` + +Defined in: [features/column-faceting/columnFacetingFeature.types.ts:102](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.types.ts#L102) + +Returns the faceted unique values for the global filter. + +#### Returns + +`Map`\<`any`, `number`\> diff --git a/docs/reference/index/interfaces/Table_ColumnFiltering.md b/docs/reference/index/interfaces/Table_ColumnFiltering.md new file mode 100644 index 0000000000..fc9a2bd057 --- /dev/null +++ b/docs/reference/index/interfaces/Table_ColumnFiltering.md @@ -0,0 +1,52 @@ +--- +id: Table_ColumnFiltering +title: Table_ColumnFiltering +--- + +# Interface: Table\_ColumnFiltering + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:177](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L177) + +## Properties + +### resetColumnFilters() + +```ts +resetColumnFilters: (defaultState?) => void; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:181](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L181) + +Resets the **columnFilters** state to `initialState.columnFilters`, or `true` can be passed to force a default blank state reset to `[]`. + +#### Parameters + +##### defaultState? + +`boolean` + +#### Returns + +`void` + +*** + +### setColumnFilters() + +```ts +setColumnFilters: (updater) => void; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:185](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L185) + +Sets column filter state using a value or updater. + +#### Parameters + +##### updater + +[`Updater`](../type-aliases/Updater.md)\<[`ColumnFiltersState`](../type-aliases/ColumnFiltersState.md)\> + +#### Returns + +`void` diff --git a/docs/reference/index/interfaces/Table_ColumnGrouping.md b/docs/reference/index/interfaces/Table_ColumnGrouping.md new file mode 100644 index 0000000000..cf768c7f93 --- /dev/null +++ b/docs/reference/index/interfaces/Table_ColumnGrouping.md @@ -0,0 +1,62 @@ +--- +id: Table_ColumnGrouping +title: Table_ColumnGrouping +--- + +# Interface: Table\_ColumnGrouping\ + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:175](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L175) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### resetGrouping() + +```ts +resetGrouping: (defaultState?) => void; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:182](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L182) + +Resets the **grouping** state to `initialState.grouping`, or `true` can be passed to force a default blank state reset to `[]`. + +#### Parameters + +##### defaultState? + +`boolean` + +#### Returns + +`void` + +*** + +### setGrouping() + +```ts +setGrouping: (updater) => void; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:186](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L186) + +Sets grouping state using a value or updater. + +#### Parameters + +##### updater + +[`Updater`](../type-aliases/Updater.md)\<[`GroupingState`](../type-aliases/GroupingState.md)\> + +#### Returns + +`void` diff --git a/docs/reference/index/interfaces/Table_ColumnOrdering.md b/docs/reference/index/interfaces/Table_ColumnOrdering.md new file mode 100644 index 0000000000..6df7341786 --- /dev/null +++ b/docs/reference/index/interfaces/Table_ColumnOrdering.md @@ -0,0 +1,62 @@ +--- +id: Table_ColumnOrdering +title: Table_ColumnOrdering +--- + +# Interface: Table\_ColumnOrdering\ + +Defined in: [features/column-ordering/columnOrderingFeature.types.ts:39](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-ordering/columnOrderingFeature.types.ts#L39) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### resetColumnOrder() + +```ts +resetColumnOrder: (defaultState?) => void; +``` + +Defined in: [features/column-ordering/columnOrderingFeature.types.ts:46](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-ordering/columnOrderingFeature.types.ts#L46) + +Resets the **columnOrder** state to `initialState.columnOrder`, or `true` can be passed to force a default blank state reset to `[]`. + +#### Parameters + +##### defaultState? + +`boolean` + +#### Returns + +`void` + +*** + +### setColumnOrder() + +```ts +setColumnOrder: (updater) => void; +``` + +Defined in: [features/column-ordering/columnOrderingFeature.types.ts:50](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-ordering/columnOrderingFeature.types.ts#L50) + +Sets column order state using a value or updater. + +#### Parameters + +##### updater + +[`Updater`](../type-aliases/Updater.md)\<[`ColumnOrderState`](../type-aliases/ColumnOrderState.md)\> + +#### Returns + +`void` diff --git a/docs/reference/index/interfaces/Table_ColumnPinning.md b/docs/reference/index/interfaces/Table_ColumnPinning.md new file mode 100644 index 0000000000..941ccfdec8 --- /dev/null +++ b/docs/reference/index/interfaces/Table_ColumnPinning.md @@ -0,0 +1,416 @@ +--- +id: Table_ColumnPinning +title: Table_ColumnPinning +--- + +# Interface: Table\_ColumnPinning\ + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:80](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L80) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### getCenterFlatHeaders() + +```ts +getCenterFlatHeaders: () => Header[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:87](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L87) + +If pinning, returns headers for all columns that are not pinned, including parent headers. + +#### Returns + +[`Header`](../type-aliases/Header.md)\<`TFeatures`, `TData`, `unknown`\>[] + +*** + +### getCenterFooterGroups() + +```ts +getCenterFooterGroups: () => HeaderGroup[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:91](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L91) + +If pinning, returns the footer groups for columns that are not pinned. + +#### Returns + +[`HeaderGroup`](../type-aliases/HeaderGroup.md)\<`TFeatures`, `TData`\>[] + +*** + +### getCenterHeaderGroups() + +```ts +getCenterHeaderGroups: () => HeaderGroup[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:95](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L95) + +If pinning, returns the header groups for columns that are not pinned. + +#### Returns + +[`HeaderGroup`](../type-aliases/HeaderGroup.md)\<`TFeatures`, `TData`\>[] + +*** + +### getCenterLeafColumns() + +```ts +getCenterLeafColumns: () => Column[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:99](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L99) + +Returns all center pinned (unpinned) leaf columns. + +#### Returns + +[`Column`](../type-aliases/Column.md)\<`TFeatures`, `TData`, `unknown`\>[] + +*** + +### getCenterLeafHeaders() + +```ts +getCenterLeafHeaders: () => Header[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:103](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L103) + +If pinning, returns headers for all columns that are not pinned, (not including parent headers). + +#### Returns + +[`Header`](../type-aliases/Header.md)\<`TFeatures`, `TData`, `unknown`\>[] + +*** + +### getCenterVisibleLeafColumns() + +```ts +getCenterVisibleLeafColumns: () => Column[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:107](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L107) + +If column pinning, returns a flat array of leaf-node columns that are visible in the unpinned/center portion of the table. + +#### Returns + +[`Column`](../type-aliases/Column.md)\<`TFeatures`, `TData`, `unknown`\>[] + +*** + +### getIsSomeColumnsPinned() + +```ts +getIsSomeColumnsPinned: (position?) => boolean; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:111](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L111) + +Returns whether or not any columns are pinned. Optionally specify to only check for pinned columns in either the `left` or `right` position. + +#### Parameters + +##### position? + +[`ColumnPinningPosition`](../type-aliases/ColumnPinningPosition.md) + +#### Returns + +`boolean` + +*** + +### getLeftFlatHeaders() + +```ts +getLeftFlatHeaders: () => Header[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:115](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L115) + +If pinning, returns headers for all left pinned columns in the table, including parent headers. + +#### Returns + +[`Header`](../type-aliases/Header.md)\<`TFeatures`, `TData`, `unknown`\>[] + +*** + +### getLeftFooterGroups() + +```ts +getLeftFooterGroups: () => HeaderGroup[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:119](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L119) + +If pinning, returns the footer groups for the left pinned columns. + +#### Returns + +[`HeaderGroup`](../type-aliases/HeaderGroup.md)\<`TFeatures`, `TData`\>[] + +*** + +### getLeftHeaderGroups() + +```ts +getLeftHeaderGroups: () => HeaderGroup[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:123](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L123) + +If pinning, returns the header groups for the left pinned columns. + +#### Returns + +[`HeaderGroup`](../type-aliases/HeaderGroup.md)\<`TFeatures`, `TData`\>[] + +*** + +### getLeftLeafColumns() + +```ts +getLeftLeafColumns: () => Column[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:127](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L127) + +Returns all left pinned leaf columns. + +#### Returns + +[`Column`](../type-aliases/Column.md)\<`TFeatures`, `TData`, `unknown`\>[] + +*** + +### getLeftLeafHeaders() + +```ts +getLeftLeafHeaders: () => Header[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:131](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L131) + +If pinning, returns headers for all left pinned leaf columns in the table, (not including parent headers). + +#### Returns + +[`Header`](../type-aliases/Header.md)\<`TFeatures`, `TData`, `unknown`\>[] + +*** + +### getLeftVisibleLeafColumns() + +```ts +getLeftVisibleLeafColumns: () => Column[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:135](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L135) + +If column pinning, returns a flat array of leaf-node columns that are visible in the left portion of the table. + +#### Returns + +[`Column`](../type-aliases/Column.md)\<`TFeatures`, `TData`, `unknown`\>[] + +*** + +### getPinnedLeafColumns() + +```ts +getPinnedLeafColumns: (position) => Column[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:171](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L171) + +Returns pinned leaf columns for the requested pinning region. + +#### Parameters + +##### position + +[`ColumnPinningPosition`](../type-aliases/ColumnPinningPosition.md) | `"center"` + +#### Returns + +[`Column`](../type-aliases/Column.md)\<`TFeatures`, `TData`, `unknown`\>[] + +*** + +### getPinnedVisibleLeafColumns() + +```ts +getPinnedVisibleLeafColumns: (position) => Column[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:177](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L177) + +Returns visible pinned leaf columns for the requested pinning region. + +#### Parameters + +##### position + +[`ColumnPinningPosition`](../type-aliases/ColumnPinningPosition.md) | `"center"` + +#### Returns + +[`Column`](../type-aliases/Column.md)\<`TFeatures`, `TData`, `unknown`\>[] + +*** + +### getRightFlatHeaders() + +```ts +getRightFlatHeaders: () => Header[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:139](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L139) + +If pinning, returns headers for all right pinned columns in the table, including parent headers. + +#### Returns + +[`Header`](../type-aliases/Header.md)\<`TFeatures`, `TData`, `unknown`\>[] + +*** + +### getRightFooterGroups() + +```ts +getRightFooterGroups: () => HeaderGroup[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:143](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L143) + +If pinning, returns the footer groups for the right pinned columns. + +#### Returns + +[`HeaderGroup`](../type-aliases/HeaderGroup.md)\<`TFeatures`, `TData`\>[] + +*** + +### getRightHeaderGroups() + +```ts +getRightHeaderGroups: () => HeaderGroup[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:147](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L147) + +If pinning, returns the header groups for the right pinned columns. + +#### Returns + +[`HeaderGroup`](../type-aliases/HeaderGroup.md)\<`TFeatures`, `TData`\>[] + +*** + +### getRightLeafColumns() + +```ts +getRightLeafColumns: () => Column[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:151](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L151) + +Returns all right pinned leaf columns. + +#### Returns + +[`Column`](../type-aliases/Column.md)\<`TFeatures`, `TData`, `unknown`\>[] + +*** + +### getRightLeafHeaders() + +```ts +getRightLeafHeaders: () => Header[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:155](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L155) + +If pinning, returns headers for all right pinned leaf columns in the table, (not including parent headers). + +#### Returns + +[`Header`](../type-aliases/Header.md)\<`TFeatures`, `TData`, `unknown`\>[] + +*** + +### getRightVisibleLeafColumns() + +```ts +getRightVisibleLeafColumns: () => Column[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:159](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L159) + +If column pinning, returns a flat array of leaf-node columns that are visible in the right portion of the table. + +#### Returns + +[`Column`](../type-aliases/Column.md)\<`TFeatures`, `TData`, `unknown`\>[] + +*** + +### resetColumnPinning() + +```ts +resetColumnPinning: (defaultState?) => void; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:163](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L163) + +Resets the **columnPinning** state to `initialState.columnPinning`, or `true` can be passed to force a default blank state reset to `{ left: [], right: [], }`. + +#### Parameters + +##### defaultState? + +`boolean` + +#### Returns + +`void` + +*** + +### setColumnPinning() + +```ts +setColumnPinning: (updater) => void; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:167](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L167) + +Sets column pinning state using a value or updater. + +#### Parameters + +##### updater + +[`Updater`](../type-aliases/Updater.md)\<[`ColumnPinningState`](ColumnPinningState.md)\> + +#### Returns + +`void` diff --git a/docs/reference/index/interfaces/Table_ColumnResizing.md b/docs/reference/index/interfaces/Table_ColumnResizing.md new file mode 100644 index 0000000000..f509e4f082 --- /dev/null +++ b/docs/reference/index/interfaces/Table_ColumnResizing.md @@ -0,0 +1,56 @@ +--- +id: Table_ColumnResizing +title: Table_ColumnResizing +--- + +# Interface: Table\_ColumnResizing + +Defined in: [features/column-resizing/columnResizingFeature.types.ts:48](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts#L48) + +## Properties + +### resetHeaderSizeInfo() + +```ts +resetHeaderSizeInfo: (defaultState?) => void; +``` + +Defined in: [features/column-resizing/columnResizingFeature.types.ts:53](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts#L53) + +Resets transient resize interaction state to `initialState.columnResizing`. +Pass `true` to reset to the feature's default blank resize state instead. + +#### Parameters + +##### defaultState? + +`boolean` + +#### Returns + +`void` + +*** + +### setcolumnResizing() + +```ts +setcolumnResizing: (updater) => void; +``` + +Defined in: [features/column-resizing/columnResizingFeature.types.ts:60](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts#L60) + +Sets the transient resize interaction state using a value or updater. + +The lowercase `c` in this API name matches the current generated v9 table +API for the `columnResizing` state slice. + +#### Parameters + +##### updater + +[`Updater`](../type-aliases/Updater.md)\<[`columnResizingState`](columnResizingState.md)\> + +#### Returns + +`void` diff --git a/docs/reference/index/interfaces/Table_ColumnSizing.md b/docs/reference/index/interfaces/Table_ColumnSizing.md new file mode 100644 index 0000000000..825397af82 --- /dev/null +++ b/docs/reference/index/interfaces/Table_ColumnSizing.md @@ -0,0 +1,117 @@ +--- +id: Table_ColumnSizing +title: Table_ColumnSizing +--- + +# Interface: Table\_ColumnSizing + +Defined in: [features/column-sizing/columnSizingFeature.types.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.types.ts#L24) + +## Properties + +### getCenterTotalSize() + +```ts +getCenterTotalSize: () => number; +``` + +Defined in: [features/column-sizing/columnSizingFeature.types.ts:28](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.types.ts#L28) + +If pinning, returns the total size of the center portion of the table by calculating the sum of the sizes of all unpinned/center leaf-columns. + +#### Returns + +`number` + +*** + +### getLeftTotalSize() + +```ts +getLeftTotalSize: () => number; +``` + +Defined in: [features/column-sizing/columnSizingFeature.types.ts:32](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.types.ts#L32) + +Returns the total size of the left portion of the table by calculating the sum of the sizes of all left leaf-columns. + +#### Returns + +`number` + +*** + +### getRightTotalSize() + +```ts +getRightTotalSize: () => number; +``` + +Defined in: [features/column-sizing/columnSizingFeature.types.ts:36](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.types.ts#L36) + +Returns the total size of the right portion of the table by calculating the sum of the sizes of all right leaf-columns. + +#### Returns + +`number` + +*** + +### getTotalSize() + +```ts +getTotalSize: () => number; +``` + +Defined in: [features/column-sizing/columnSizingFeature.types.ts:40](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.types.ts#L40) + +Returns the total size of the table by calculating the sum of the sizes of all leaf-columns. + +#### Returns + +`number` + +*** + +### resetColumnSizing() + +```ts +resetColumnSizing: (defaultState?) => void; +``` + +Defined in: [features/column-sizing/columnSizingFeature.types.ts:45](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.types.ts#L45) + +Resets column sizing to `initialState.columnSizing`. Pass `true` to reset +to the feature default of `{}`. + +#### Parameters + +##### defaultState? + +`boolean` + +#### Returns + +`void` + +*** + +### setColumnSizing() + +```ts +setColumnSizing: (updater) => void; +``` + +Defined in: [features/column-sizing/columnSizingFeature.types.ts:49](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.types.ts#L49) + +Sets committed column sizing state using a value or updater. + +#### Parameters + +##### updater + +[`Updater`](../type-aliases/Updater.md)\<[`ColumnSizingState`](../type-aliases/ColumnSizingState.md)\> + +#### Returns + +`void` diff --git a/docs/reference/index/interfaces/Table_ColumnVisibility.md b/docs/reference/index/interfaces/Table_ColumnVisibility.md new file mode 100644 index 0000000000..69278a3a84 --- /dev/null +++ b/docs/reference/index/interfaces/Table_ColumnVisibility.md @@ -0,0 +1,176 @@ +--- +id: Table_ColumnVisibility +title: Table_ColumnVisibility +--- + +# Interface: Table\_ColumnVisibility\ + +Defined in: [features/column-visibility/columnVisibilityFeature.types.ts:30](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.types.ts#L30) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### getIsAllColumnsVisible() + +```ts +getIsAllColumnsVisible: () => boolean; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.types.ts:37](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.types.ts#L37) + +Returns whether all columns are visible + +#### Returns + +`boolean` + +*** + +### getIsSomeColumnsVisible() + +```ts +getIsSomeColumnsVisible: () => boolean; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.types.ts:41](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.types.ts#L41) + +Returns whether any columns are visible + +#### Returns + +`boolean` + +*** + +### getToggleAllColumnsVisibilityHandler() + +```ts +getToggleAllColumnsVisibilityHandler: () => (event) => void; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.types.ts:45](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.types.ts#L45) + +Returns a handler for toggling the visibility of all columns, meant to be bound to a `input[type=checkbox]` element. + +#### Returns + +```ts +(event): void; +``` + +##### Parameters + +###### event + +`unknown` + +##### Returns + +`void` + +*** + +### getVisibleFlatColumns() + +```ts +getVisibleFlatColumns: () => Column[]; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.types.ts:49](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.types.ts#L49) + +Returns a flat array of columns that are visible, including parent columns. + +#### Returns + +[`Column`](../type-aliases/Column.md)\<`TFeatures`, `TData`, `unknown`\>[] + +*** + +### getVisibleLeafColumns() + +```ts +getVisibleLeafColumns: () => Column[]; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.types.ts:53](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.types.ts#L53) + +Returns a flat array of leaf-node columns that are visible. + +#### Returns + +[`Column`](../type-aliases/Column.md)\<`TFeatures`, `TData`, `unknown`\>[] + +*** + +### resetColumnVisibility() + +```ts +resetColumnVisibility: (defaultState?) => void; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.types.ts:57](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.types.ts#L57) + +Resets the column visibility state to the initial state. If `defaultState` is provided, the state will be reset to `{}` + +#### Parameters + +##### defaultState? + +`boolean` + +#### Returns + +`void` + +*** + +### setColumnVisibility() + +```ts +setColumnVisibility: (updater) => void; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.types.ts:61](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.types.ts#L61) + +Sets column visibility state using a value or updater. + +#### Parameters + +##### updater + +[`Updater`](../type-aliases/Updater.md)\<[`ColumnVisibilityState`](../type-aliases/ColumnVisibilityState.md)\> + +#### Returns + +`void` + +*** + +### toggleAllColumnsVisible() + +```ts +toggleAllColumnsVisible: (value?) => void; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.types.ts:65](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.types.ts#L65) + +Toggles the visibility of all columns. + +#### Parameters + +##### value? + +`boolean` + +#### Returns + +`void` diff --git a/docs/reference/index/interfaces/Table_Columns.md b/docs/reference/index/interfaces/Table_Columns.md new file mode 100644 index 0000000000..e337a16245 --- /dev/null +++ b/docs/reference/index/interfaces/Table_Columns.md @@ -0,0 +1,123 @@ +--- +id: Table_Columns +title: Table_Columns +--- + +# Interface: Table\_Columns\ + +Defined in: [core/columns/coreColumnsFeature.types.ts:75](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L75) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### getAllColumns() + +```ts +getAllColumns: () => Column[]; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:90](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L90) + +Returns all columns in the table in their normalized and nested hierarchy. + +#### Returns + +[`Column`](../type-aliases/Column.md)\<`TFeatures`, `TData`, `unknown`\>[] + +*** + +### getAllFlatColumns() + +```ts +getAllFlatColumns: () => Column[]; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:94](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L94) + +Returns all columns in the table flattened to a single level. + +#### Returns + +[`Column`](../type-aliases/Column.md)\<`TFeatures`, `TData`, `unknown`\>[] + +*** + +### getAllFlatColumnsById() + +```ts +getAllFlatColumnsById: () => Record>; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:82](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L82) + +Returns a map of all flat columns by their ID. + +#### Returns + +`Record`\<`string`, [`Column`](../type-aliases/Column.md)\<`TFeatures`, `TData`, `unknown`\>\> + +*** + +### getAllLeafColumns() + +```ts +getAllLeafColumns: () => Column[]; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:98](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L98) + +Returns all leaf-node columns in the table flattened to a single level. This does not include parent columns. + +#### Returns + +[`Column`](../type-aliases/Column.md)\<`TFeatures`, `TData`, `unknown`\>[] + +*** + +### getColumn() + +```ts +getColumn: (columnId) => + | Column + | undefined; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:102](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L102) + +Returns a single column by its ID. + +#### Parameters + +##### columnId + +`string` + +#### Returns + + \| [`Column`](../type-aliases/Column.md)\<`TFeatures`, `TData`, `unknown`\> + \| `undefined` + +*** + +### getDefaultColumnDef() + +```ts +getDefaultColumnDef: () => Partial>; +``` + +Defined in: [core/columns/coreColumnsFeature.types.ts:86](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.types.ts#L86) + +Returns the default column options to use for all column defs supplied to the table. + +#### Returns + +`Partial`\<[`ColumnDef`](../type-aliases/ColumnDef.md)\<`TFeatures`, `TData`, `unknown`\>\> diff --git a/docs/reference/index/interfaces/Table_CoreProperties.md b/docs/reference/index/interfaces/Table_CoreProperties.md new file mode 100644 index 0000000000..fe332e5137 --- /dev/null +++ b/docs/reference/index/interfaces/Table_CoreProperties.md @@ -0,0 +1,196 @@ +--- +id: Table_CoreProperties +title: Table_CoreProperties +--- + +# Interface: Table\_CoreProperties\ + +Defined in: [core/table/coreTablesFeature.types.ts:120](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L120) + +## Extended by + +- [`Table_Table`](Table_Table.md) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### \_cellPrototype? + +```ts +optional _cellPrototype: object; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:131](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L131) + +Prototype cache for Cell objects - shared by all cells in this table + +*** + +### \_columnPrototype? + +```ts +optional _columnPrototype: object; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:135](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L135) + +Prototype cache for Column objects - shared by all columns in this table + +*** + +### \_features + +```ts +readonly _features: Partial & TFeatures; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:139](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L139) + +The features that are enabled for the table. + +*** + +### \_headerPrototype? + +```ts +optional _headerPrototype: object; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:143](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L143) + +Prototype cache for Header objects - shared by all headers in this table + +*** + +### \_reactivity + +```ts +readonly _reactivity: TableReactivityBindings; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:127](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L127) + +Table reactivity bindings for interacting with TanStack Store. + +*** + +### \_rowModelFns + +```ts +readonly _rowModelFns: RowModelFns; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:147](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L147) + +The row model processing functions that are used to process the data by features. + +*** + +### \_rowModels + +```ts +readonly _rowModels: CachedRowModels; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:151](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L151) + +The row models that are enabled for the table. + +*** + +### \_rowPrototype? + +```ts +optional _rowPrototype: object; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:155](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L155) + +Prototype cache for Row objects - shared by all rows in this table + +*** + +### atoms + +```ts +readonly atoms: Atoms; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:161](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L161) + +The readonly derived atoms for each `TableState` slice. Each derives from +its corresponding `baseAtom` plus, optionally, a per-slice external atom or +external state value (precedence: external atom > external state > base atom). + +*** + +### baseAtoms + +```ts +readonly baseAtoms: BaseAtoms; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:166](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L166) + +The internal writable atoms for each `TableState` slice. This is the library's +single write surface — all state mutations from features land here. + +*** + +### initialState + +```ts +readonly initialState: TableState; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:170](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L170) + +This is the resolved initial state of the table. + +*** + +### options + +```ts +readonly options: TableOptions; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:174](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L174) + +A read-only reference to the table's current options. + +*** + +### optionsStore? + +```ts +readonly optional optionsStore: Atom>; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:180](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L180) + +Writable atom for table options. Only created when `createOptionsStore` is +true on the active core reactivity bindings. Adapters that opt out keep +options as plain resolved data instead of backing them with an atom. + +*** + +### store + +```ts +readonly store: ReadonlyStore>; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:185](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L185) + +The readonly flat store for the table state. Derives from `table.atoms` +only; never reads external state directly. diff --git a/docs/reference/index/interfaces/Table_GlobalFiltering.md b/docs/reference/index/interfaces/Table_GlobalFiltering.md new file mode 100644 index 0000000000..97013e163e --- /dev/null +++ b/docs/reference/index/interfaces/Table_GlobalFiltering.md @@ -0,0 +1,94 @@ +--- +id: Table_GlobalFiltering +title: Table_GlobalFiltering +--- + +# Interface: Table\_GlobalFiltering\ + +Defined in: [features/global-filtering/globalFilteringFeature.types.ts:66](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.types.ts#L66) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### getGlobalAutoFilterFn() + +```ts +getGlobalAutoFilterFn: () => FilterFn | undefined; +``` + +Defined in: [features/global-filtering/globalFilteringFeature.types.ts:73](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.types.ts#L73) + +Currently, this function returns the built-in `includesString` filter function. In future releases, it may return more dynamic filter functions based on the nature of the data provided. + +#### Returns + +[`FilterFn`](FilterFn.md)\<`TFeatures`, `TData`\> \| `undefined` + +*** + +### getGlobalFilterFn() + +```ts +getGlobalFilterFn: () => FilterFn | undefined; +``` + +Defined in: [features/global-filtering/globalFilteringFeature.types.ts:77](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.types.ts#L77) + +Returns the filter function (either user-defined or automatic, depending on configuration) for the global filter. + +#### Returns + +[`FilterFn`](FilterFn.md)\<`TFeatures`, `TData`\> \| `undefined` + +*** + +### resetGlobalFilter() + +```ts +resetGlobalFilter: (defaultState?) => void; +``` + +Defined in: [features/global-filtering/globalFilteringFeature.types.ts:81](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.types.ts#L81) + +Resets the **globalFilter** state to `initialState.globalFilter`, or `true` can be passed to force a default blank state reset to `undefined`. + +#### Parameters + +##### defaultState? + +`boolean` + +#### Returns + +`void` + +*** + +### setGlobalFilter() + +```ts +setGlobalFilter: (updater) => void; +``` + +Defined in: [features/global-filtering/globalFilteringFeature.types.ts:85](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.types.ts#L85) + +Sets global filter state using a value or updater. + +#### Parameters + +##### updater + +`any` + +#### Returns + +`void` diff --git a/docs/reference/index/interfaces/Table_Headers.md b/docs/reference/index/interfaces/Table_Headers.md new file mode 100644 index 0000000000..96cd15bf66 --- /dev/null +++ b/docs/reference/index/interfaces/Table_Headers.md @@ -0,0 +1,82 @@ +--- +id: Table_Headers +title: Table_Headers +--- + +# Interface: Table\_Headers\ + +Defined in: [core/headers/coreHeadersFeature.types.ts:8](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L8) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### getFlatHeaders() + +```ts +getFlatHeaders: () => Header[]; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:23](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L23) + +Returns headers for all columns in the table, including parent headers. + +#### Returns + +[`Header`](../type-aliases/Header.md)\<`TFeatures`, `TData`, `unknown`\>[] + +*** + +### getFooterGroups() + +```ts +getFooterGroups: () => HeaderGroup[]; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:19](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L19) + +Returns the footer groups for the table. + +#### Returns + +[`HeaderGroup`](../type-aliases/HeaderGroup.md)\<`TFeatures`, `TData`\>[] + +*** + +### getHeaderGroups() + +```ts +getHeaderGroups: () => HeaderGroup[]; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:15](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L15) + +Returns all header groups for the table. + +#### Returns + +[`HeaderGroup`](../type-aliases/HeaderGroup.md)\<`TFeatures`, `TData`\>[] + +*** + +### getLeafHeaders() + +```ts +getLeafHeaders: () => Header[]; +``` + +Defined in: [core/headers/coreHeadersFeature.types.ts:27](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.types.ts#L27) + +Returns headers for all leaf columns in the table, (not including parent headers). + +#### Returns + +[`Header`](../type-aliases/Header.md)\<`TFeatures`, `TData`, `unknown`\>[] diff --git a/docs/reference/index/interfaces/Table_Plugins.md b/docs/reference/index/interfaces/Table_Plugins.md new file mode 100644 index 0000000000..874bd90101 --- /dev/null +++ b/docs/reference/index/interfaces/Table_Plugins.md @@ -0,0 +1,21 @@ +--- +id: Table_Plugins +title: Table_Plugins +--- + +# Interface: Table\_Plugins\ + +Defined in: [types/Table.ts:37](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L37) + +Use this interface as a target for declaration merging to add your own plugin properties. +Note: This will affect the types of all tables in your project. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) diff --git a/docs/reference/index/interfaces/Table_RowExpanding.md b/docs/reference/index/interfaces/Table_RowExpanding.md new file mode 100644 index 0000000000..3e8b25c82f --- /dev/null +++ b/docs/reference/index/interfaces/Table_RowExpanding.md @@ -0,0 +1,191 @@ +--- +id: Table_RowExpanding +title: Table_RowExpanding +--- + +# Interface: Table\_RowExpanding\ + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:73](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L73) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### autoResetExpanded() + +```ts +autoResetExpanded: () => void; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:77](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L77) + +#### Returns + +`void` + +*** + +### getCanSomeRowsExpand() + +```ts +getCanSomeRowsExpand: () => boolean; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:81](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L81) + +Returns whether there are any rows that can be expanded. + +#### Returns + +`boolean` + +*** + +### getExpandedDepth() + +```ts +getExpandedDepth: () => number; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:85](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L85) + +Returns the maximum depth of the expanded rows. + +#### Returns + +`number` + +*** + +### getIsAllRowsExpanded() + +```ts +getIsAllRowsExpanded: () => boolean; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:89](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L89) + +Returns whether all rows are currently expanded. + +#### Returns + +`boolean` + +*** + +### getIsSomeRowsExpanded() + +```ts +getIsSomeRowsExpanded: () => boolean; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:93](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L93) + +Returns whether there are any rows that are currently expanded. + +#### Returns + +`boolean` + +*** + +### getToggleAllRowsExpandedHandler() + +```ts +getToggleAllRowsExpandedHandler: () => (event) => void; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:97](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L97) + +Returns a handler that can be used to toggle the expanded state of all rows. This handler is meant to be used with an `input[type=checkbox]` element. + +#### Returns + +```ts +(event): void; +``` + +##### Parameters + +###### event + +`unknown` + +##### Returns + +`void` + +*** + +### resetExpanded() + +```ts +resetExpanded: (defaultState?) => void; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:102](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L102) + +Resets expanded state to `initialState.expanded`. Pass `true` to reset to +the feature default of `{}`. + +#### Parameters + +##### defaultState? + +`boolean` + +#### Returns + +`void` + +*** + +### setExpanded() + +```ts +setExpanded: (updater) => void; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:106](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L106) + +Sets expanded state using a value or updater. + +#### Parameters + +##### updater + +[`Updater`](../type-aliases/Updater.md)\<[`ExpandedState`](../type-aliases/ExpandedState.md)\> + +#### Returns + +`void` + +*** + +### toggleAllRowsExpanded() + +```ts +toggleAllRowsExpanded: (expanded?) => void; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:110](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L110) + +Toggles the expanded state for all rows. + +#### Parameters + +##### expanded? + +`boolean` + +#### Returns + +`void` diff --git a/docs/reference/index/interfaces/Table_RowModels_Core.md b/docs/reference/index/interfaces/Table_RowModels_Core.md new file mode 100644 index 0000000000..0c6263a8b7 --- /dev/null +++ b/docs/reference/index/interfaces/Table_RowModels_Core.md @@ -0,0 +1,50 @@ +--- +id: Table_RowModels_Core +title: Table_RowModels_Core +--- + +# Interface: Table\_RowModels\_Core\ + +Defined in: [core/row-models/coreRowModelsFeature.types.ts:45](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.types.ts#L45) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### getCoreRowModel() + +```ts +getCoreRowModel: () => RowModel; +``` + +Defined in: [core/row-models/coreRowModelsFeature.types.ts:52](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.types.ts#L52) + +Returns the core row model before any processing has been applied. + +#### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> + +*** + +### getRowModel() + +```ts +getRowModel: () => RowModel; +``` + +Defined in: [core/row-models/coreRowModelsFeature.types.ts:56](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.types.ts#L56) + +Returns the final model after all processing from other used features has been applied. This is the row model that is most commonly used for rendering. + +#### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/interfaces/Table_RowModels_Expanded.md b/docs/reference/index/interfaces/Table_RowModels_Expanded.md new file mode 100644 index 0000000000..49e884b347 --- /dev/null +++ b/docs/reference/index/interfaces/Table_RowModels_Expanded.md @@ -0,0 +1,50 @@ +--- +id: Table_RowModels_Expanded +title: Table_RowModels_Expanded +--- + +# Interface: Table\_RowModels\_Expanded\ + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:113](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L113) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### getExpandedRowModel() + +```ts +getExpandedRowModel: () => RowModel; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:120](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L120) + +Returns the row model after expansion has been applied. + +#### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> + +*** + +### getPreExpandedRowModel() + +```ts +getPreExpandedRowModel: () => RowModel; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:124](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L124) + +Returns the row model before expansion has been applied. + +#### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/interfaces/Table_RowModels_Faceted.md b/docs/reference/index/interfaces/Table_RowModels_Faceted.md new file mode 100644 index 0000000000..6c211f4228 --- /dev/null +++ b/docs/reference/index/interfaces/Table_RowModels_Faceted.md @@ -0,0 +1,69 @@ +--- +id: Table_RowModels_Faceted +title: Table_RowModels_Faceted +--- + +# Interface: Table\_RowModels\_Faceted\ + +Defined in: [features/column-faceting/columnFacetingFeature.types.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.types.ts#L24) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### getFacetedMinMaxValues() + +```ts +getFacetedMinMaxValues: () => [number, number] | undefined; +``` + +Defined in: [features/column-faceting/columnFacetingFeature.types.ts:32](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.types.ts#L32) + +A function that **computes and returns** a min/max tuple derived from `column.getFacetedRowModel`. Useful for displaying faceted result values. +> Requires that you pass a valid `facetedMinMaxValues` row model factory in `_rowModels`. A default implementation is provided via the exported `createFacetedMinMaxValues` function. + +#### Returns + +\[`number`, `number`\] \| `undefined` + +*** + +### getFacetedRowModel() + +```ts +getFacetedRowModel: () => RowModel; +``` + +Defined in: [features/column-faceting/columnFacetingFeature.types.ts:37](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.types.ts#L37) + +Returns the row model with all other column filters applied, excluding its own filter. Useful for displaying faceted result counts. +> Requires that you pass a valid `facetedRowModel` row model factory in `_rowModels`. A default implementation is provided via the exported `createFacetedRowModel` function. + +#### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> + +*** + +### getFacetedUniqueValues() + +```ts +getFacetedUniqueValues: () => Map; +``` + +Defined in: [features/column-faceting/columnFacetingFeature.types.ts:42](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.types.ts#L42) + +A function that **computes and returns** a `Map` of unique values and their occurrences derived from `column.getFacetedRowModel`. Useful for displaying faceted result values. +> Requires that you pass a valid `facetedUniqueValues` row model factory in `_rowModels`. A default implementation is provided via the exported `createFacetedUniqueValues` function. + +#### Returns + +`Map`\<`any`, `number`\> diff --git a/docs/reference/index/interfaces/Table_RowModels_Filtered.md b/docs/reference/index/interfaces/Table_RowModels_Filtered.md new file mode 100644 index 0000000000..a0e8e02edc --- /dev/null +++ b/docs/reference/index/interfaces/Table_RowModels_Filtered.md @@ -0,0 +1,50 @@ +--- +id: Table_RowModels_Filtered +title: Table_RowModels_Filtered +--- + +# Interface: Table\_RowModels\_Filtered\ + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:188](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L188) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### getFilteredRowModel() + +```ts +getFilteredRowModel: () => RowModel; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:195](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L195) + +Returns the row model for the table after **column** filtering has been applied. + +#### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> + +*** + +### getPreFilteredRowModel() + +```ts +getPreFilteredRowModel: () => RowModel; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:199](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L199) + +Returns the row model for the table before any **column** filtering has been applied. + +#### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/interfaces/Table_RowModels_Grouped.md b/docs/reference/index/interfaces/Table_RowModels_Grouped.md new file mode 100644 index 0000000000..8c2cd04a8c --- /dev/null +++ b/docs/reference/index/interfaces/Table_RowModels_Grouped.md @@ -0,0 +1,50 @@ +--- +id: Table_RowModels_Grouped +title: Table_RowModels_Grouped +--- + +# Interface: Table\_RowModels\_Grouped\ + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:189](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L189) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### getGroupedRowModel() + +```ts +getGroupedRowModel: () => RowModel; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:196](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L196) + +Returns the row model for the table after grouping has been applied. + +#### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> + +*** + +### getPreGroupedRowModel() + +```ts +getPreGroupedRowModel: () => RowModel; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:200](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L200) + +Returns the row model for the table before any grouping has been applied. + +#### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/interfaces/Table_RowModels_Paginated.md b/docs/reference/index/interfaces/Table_RowModels_Paginated.md new file mode 100644 index 0000000000..5f79059b37 --- /dev/null +++ b/docs/reference/index/interfaces/Table_RowModels_Paginated.md @@ -0,0 +1,50 @@ +--- +id: Table_RowModels_Paginated +title: Table_RowModels_Paginated +--- + +# Interface: Table\_RowModels\_Paginated\ + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:112](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L112) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### getPaginatedRowModel() + +```ts +getPaginatedRowModel: () => RowModel; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:119](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L119) + +Returns the row model for the table after pagination has been applied. + +#### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> + +*** + +### getPrePaginatedRowModel() + +```ts +getPrePaginatedRowModel: () => RowModel; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:123](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L123) + +Returns the row model for the table before any pagination has been applied. + +#### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/interfaces/Table_RowModels_Sorted.md b/docs/reference/index/interfaces/Table_RowModels_Sorted.md new file mode 100644 index 0000000000..e7f7f6c455 --- /dev/null +++ b/docs/reference/index/interfaces/Table_RowModels_Sorted.md @@ -0,0 +1,50 @@ +--- +id: Table_RowModels_Sorted +title: Table_RowModels_Sorted +--- + +# Interface: Table\_RowModels\_Sorted\ + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:200](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L200) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### getPreSortedRowModel() + +```ts +getPreSortedRowModel: () => RowModel; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:207](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L207) + +Returns the row model for the table before any sorting has been applied. + +#### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> + +*** + +### getSortedRowModel() + +```ts +getSortedRowModel: () => RowModel; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:211](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L211) + +Returns the row model for the table after sorting has been applied. + +#### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> diff --git a/docs/reference/index/interfaces/Table_RowPagination.md b/docs/reference/index/interfaces/Table_RowPagination.md new file mode 100644 index 0000000000..2b71f842bf --- /dev/null +++ b/docs/reference/index/interfaces/Table_RowPagination.md @@ -0,0 +1,309 @@ +--- +id: Table_RowPagination +title: Table_RowPagination +--- + +# Interface: Table\_RowPagination\ + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:44](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L44) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### \_autoResetPageIndex() + +```ts +_autoResetPageIndex: () => void; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:48](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L48) + +#### Returns + +`void` + +*** + +### firstPage() + +```ts +firstPage: () => void; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:80](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L80) + +Sets the page index to `0`. + +#### Returns + +`void` + +*** + +### getCanNextPage() + +```ts +getCanNextPage: () => boolean; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:52](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L52) + +Returns whether the table can go to the next page. + +#### Returns + +`boolean` + +*** + +### getCanPreviousPage() + +```ts +getCanPreviousPage: () => boolean; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:56](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L56) + +Returns whether the table can go to the previous page. + +#### Returns + +`boolean` + +*** + +### getPageCount() + +```ts +getPageCount: () => number; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:60](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L60) + +Returns the page count. If manually paginating or controlling the pagination state, this will come directly from the `options.pageCount` table option, otherwise it will be calculated from the table data using the total row count and current page size. + +#### Returns + +`number` + +*** + +### getPageOptions() + +```ts +getPageOptions: () => number[]; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:68](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L68) + +Returns an array of page options (zero-index-based) for the current page size. + +#### Returns + +`number`[] + +*** + +### getRowCount() + +```ts +getRowCount: () => number; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:64](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L64) + +Returns the row count. If manually paginating or controlling the pagination state, this will come directly from the `options.rowCount` table option, otherwise it will be calculated from the table data. + +#### Returns + +`number` + +*** + +### lastPage() + +```ts +lastPage: () => void; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:84](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L84) + +Sets the page index to the last page. + +#### Returns + +`void` + +*** + +### nextPage() + +```ts +nextPage: () => void; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:72](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L72) + +Increments the page index by one, if possible. + +#### Returns + +`void` + +*** + +### previousPage() + +```ts +previousPage: () => void; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:76](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L76) + +Decrements the page index by one, if possible. + +#### Returns + +`void` + +*** + +### resetPageIndex() + +```ts +resetPageIndex: (defaultState?) => void; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:88](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L88) + +Resets the page index to its initial state. If `defaultState` is `true`, the page index will be reset to `0` regardless of initial state. + +#### Parameters + +##### defaultState? + +`boolean` + +#### Returns + +`void` + +*** + +### resetPageSize() + +```ts +resetPageSize: (defaultState?) => void; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:92](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L92) + +Resets the page size to its initial state. If `defaultState` is `true`, the page size will be reset to `10` regardless of initial state. + +#### Parameters + +##### defaultState? + +`boolean` + +#### Returns + +`void` + +*** + +### resetPagination() + +```ts +resetPagination: (defaultState?) => void; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:97](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L97) + +Resets pagination state to `initialState.pagination`. Pass `true` to reset +to the feature default of `{ pageIndex: 0, pageSize: 10 }`. + +#### Parameters + +##### defaultState? + +`boolean` + +#### Returns + +`void` + +*** + +### setPageIndex() + +```ts +setPageIndex: (updater) => void; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:101](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L101) + +Updates `pagination.pageIndex` using a value or updater. + +#### Parameters + +##### updater + +[`Updater`](../type-aliases/Updater.md)\<`number`\> + +#### Returns + +`void` + +*** + +### setPageSize() + +```ts +setPageSize: (updater) => void; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:105](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L105) + +Updates `pagination.pageSize` using a value or updater. + +#### Parameters + +##### updater + +[`Updater`](../type-aliases/Updater.md)\<`number`\> + +#### Returns + +`void` + +*** + +### setPagination() + +```ts +setPagination: (updater) => void; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.types.ts:109](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts#L109) + +Sets pagination state using a value or updater. + +#### Parameters + +##### updater + +[`Updater`](../type-aliases/Updater.md)\<[`PaginationState`](PaginationState.md)\> + +#### Returns + +`void` diff --git a/docs/reference/index/interfaces/Table_RowPinning.md b/docs/reference/index/interfaces/Table_RowPinning.md new file mode 100644 index 0000000000..c7da49f21a --- /dev/null +++ b/docs/reference/index/interfaces/Table_RowPinning.md @@ -0,0 +1,132 @@ +--- +id: Table_RowPinning +title: Table_RowPinning +--- + +# Interface: Table\_RowPinning\ + +Defined in: [features/row-pinning/rowPinningFeature.types.ts:63](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.types.ts#L63) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### getBottomRows() + +```ts +getBottomRows: () => Row[]; +``` + +Defined in: [features/row-pinning/rowPinningFeature.types.ts:70](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.types.ts#L70) + +Returns all bottom pinned rows. + +#### Returns + +[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\>[] + +*** + +### getCenterRows() + +```ts +getCenterRows: () => Row[]; +``` + +Defined in: [features/row-pinning/rowPinningFeature.types.ts:74](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.types.ts#L74) + +Returns all rows that are not pinned to the top or bottom. + +#### Returns + +[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\>[] + +*** + +### getIsSomeRowsPinned() + +```ts +getIsSomeRowsPinned: (position?) => boolean; +``` + +Defined in: [features/row-pinning/rowPinningFeature.types.ts:78](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.types.ts#L78) + +Returns whether or not any rows are pinned. Optionally specify to only check for pinned rows in either the `top` or `bottom` position. + +#### Parameters + +##### position? + +[`RowPinningPosition`](../type-aliases/RowPinningPosition.md) + +#### Returns + +`boolean` + +*** + +### getTopRows() + +```ts +getTopRows: () => Row[]; +``` + +Defined in: [features/row-pinning/rowPinningFeature.types.ts:82](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.types.ts#L82) + +Returns all top pinned rows. + +#### Returns + +[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\>[] + +*** + +### resetRowPinning() + +```ts +resetRowPinning: (defaultState?) => void; +``` + +Defined in: [features/row-pinning/rowPinningFeature.types.ts:86](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.types.ts#L86) + +Resets the **rowPinning** state to `initialState.rowPinning`, or `true` can be passed to force a default blank state reset to `{ top: [], bottom: [], }`. + +#### Parameters + +##### defaultState? + +`boolean` + +#### Returns + +`void` + +*** + +### setRowPinning() + +```ts +setRowPinning: (updater) => void; +``` + +Defined in: [features/row-pinning/rowPinningFeature.types.ts:90](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.types.ts#L90) + +Sets row pinning state using a value or updater. + +#### Parameters + +##### updater + +[`Updater`](../type-aliases/Updater.md)\<[`RowPinningState`](RowPinningState.md)\> + +#### Returns + +`void` diff --git a/docs/reference/index/interfaces/Table_RowSelection.md b/docs/reference/index/interfaces/Table_RowSelection.md new file mode 100644 index 0000000000..73129a7d58 --- /dev/null +++ b/docs/reference/index/interfaces/Table_RowSelection.md @@ -0,0 +1,290 @@ +--- +id: Table_RowSelection +title: Table_RowSelection +--- + +# Interface: Table\_RowSelection\ + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:85](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L85) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### getFilteredSelectedRowModel() + +```ts +getFilteredSelectedRowModel: () => RowModel; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:92](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L92) + +Returns the row model of all rows that are selected after filtering has been applied. + +#### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> + +*** + +### getGroupedSelectedRowModel() + +```ts +getGroupedSelectedRowModel: () => RowModel; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:96](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L96) + +Returns the row model of all rows that are selected after grouping has been applied. + +#### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> + +*** + +### getIsAllPageRowsSelected() + +```ts +getIsAllPageRowsSelected: () => boolean; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:100](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L100) + +Returns whether or not all rows on the current page are selected. + +#### Returns + +`boolean` + +*** + +### getIsAllRowsSelected() + +```ts +getIsAllRowsSelected: () => boolean; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:104](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L104) + +Returns whether or not all rows in the table are selected. + +#### Returns + +`boolean` + +*** + +### getIsSomePageRowsSelected() + +```ts +getIsSomePageRowsSelected: () => boolean; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:108](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L108) + +Returns whether or not any rows on the current page are selected. + +#### Returns + +`boolean` + +*** + +### getIsSomeRowsSelected() + +```ts +getIsSomeRowsSelected: () => boolean; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:112](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L112) + +Returns whether or not any rows in the table are selected. + +#### Returns + +`boolean` + +*** + +### getPreSelectedRowModel() + +```ts +getPreSelectedRowModel: () => RowModel; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:116](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L116) + +Returns the core row model of all rows before row selection has been applied. + +#### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> + +*** + +### getSelectedRowModel() + +```ts +getSelectedRowModel: () => RowModel; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:120](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L120) + +Returns the row model of all rows that are selected. + +#### Returns + +[`RowModel`](RowModel.md)\<`TFeatures`, `TData`\> + +*** + +### getToggleAllPageRowsSelectedHandler() + +```ts +getToggleAllPageRowsSelectedHandler: () => (event) => void; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:124](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L124) + +Returns a handler that can be used to toggle all rows on the current page. + +#### Returns + +```ts +(event): void; +``` + +##### Parameters + +###### event + +`unknown` + +##### Returns + +`void` + +*** + +### getToggleAllRowsSelectedHandler() + +```ts +getToggleAllRowsSelectedHandler: () => (event) => void; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:128](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L128) + +Returns a handler that can be used to toggle all rows in the table. + +#### Returns + +```ts +(event): void; +``` + +##### Parameters + +###### event + +`unknown` + +##### Returns + +`void` + +*** + +### resetRowSelection() + +```ts +resetRowSelection: (defaultState?) => void; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:132](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L132) + +Resets the **rowSelection** state to the `initialState.rowSelection`, or `true` can be passed to force a default blank state reset to `{}`. + +#### Parameters + +##### defaultState? + +`boolean` + +#### Returns + +`void` + +*** + +### setRowSelection() + +```ts +setRowSelection: (updater) => void; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:136](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L136) + +Sets row selection state using a value or updater. + +#### Parameters + +##### updater + +[`Updater`](../type-aliases/Updater.md)\<[`RowSelectionState`](../type-aliases/RowSelectionState.md)\> + +#### Returns + +`void` + +*** + +### toggleAllPageRowsSelected() + +```ts +toggleAllPageRowsSelected: (value?) => void; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:140](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L140) + +Selects/deselects all rows on the current page. + +#### Parameters + +##### value? + +`boolean` + +#### Returns + +`void` + +*** + +### toggleAllRowsSelected() + +```ts +toggleAllRowsSelected: (value?) => void; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:144](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L144) + +Selects/deselects all rows in the table. + +#### Parameters + +##### value? + +`boolean` + +#### Returns + +`void` diff --git a/docs/reference/index/interfaces/Table_RowSorting.md b/docs/reference/index/interfaces/Table_RowSorting.md new file mode 100644 index 0000000000..2fc1ab2f87 --- /dev/null +++ b/docs/reference/index/interfaces/Table_RowSorting.md @@ -0,0 +1,62 @@ +--- +id: Table_RowSorting +title: Table_RowSorting +--- + +# Interface: Table\_RowSorting\ + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:186](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L186) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### resetSorting() + +```ts +resetSorting: (defaultState?) => void; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:193](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L193) + +Resets the **sorting** state to `initialState.sorting`, or `true` can be passed to force a default blank state reset to `[]`. + +#### Parameters + +##### defaultState? + +`boolean` + +#### Returns + +`void` + +*** + +### setSorting() + +```ts +setSorting: (updater) => void; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:197](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L197) + +Sets sorting state using a value or updater. + +#### Parameters + +##### updater + +[`Updater`](../type-aliases/Updater.md)\<[`SortingState`](../type-aliases/SortingState.md)\> + +#### Returns + +`void` diff --git a/docs/reference/index/interfaces/Table_Rows.md b/docs/reference/index/interfaces/Table_Rows.md new file mode 100644 index 0000000000..94dddefb98 --- /dev/null +++ b/docs/reference/index/interfaces/Table_Rows.md @@ -0,0 +1,72 @@ +--- +id: Table_Rows +title: Table_Rows +--- + +# Interface: Table\_Rows\ + +Defined in: [core/rows/coreRowsFeature.types.ts:105](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L105) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### getRow() + +```ts +getRow: (id, searchAll?) => Row; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:113](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L113) + +Returns the row with the given ID. + +#### Parameters + +##### id + +`string` + +##### searchAll? + +`boolean` + +#### Returns + +[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\> + +*** + +### getRowId() + +```ts +getRowId: (_, index, parent?) => string; +``` + +Defined in: [core/rows/coreRowsFeature.types.ts:109](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.types.ts#L109) + +#### Parameters + +##### \_ + +`TData` + +##### index + +`number` + +##### parent? + +[`Row`](../type-aliases/Row.md)\<`TFeatures`, `TData`\> + +#### Returns + +`string` diff --git a/docs/reference/index/interfaces/Table_Table.md b/docs/reference/index/interfaces/Table_Table.md new file mode 100644 index 0000000000..b8d7234f86 --- /dev/null +++ b/docs/reference/index/interfaces/Table_Table.md @@ -0,0 +1,295 @@ +--- +id: Table_Table +title: Table_Table +--- + +# Interface: Table\_Table\ + +Defined in: [core/table/coreTablesFeature.types.ts:188](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L188) + +## Extends + +- [`Table_CoreProperties`](Table_CoreProperties.md)\<`TFeatures`, `TData`\> + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../type-aliases/RowData.md) + +## Properties + +### \_cellPrototype? + +```ts +optional _cellPrototype: object; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:131](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L131) + +Prototype cache for Cell objects - shared by all cells in this table + +#### Inherited from + +[`Table_CoreProperties`](Table_CoreProperties.md).[`_cellPrototype`](Table_CoreProperties.md#_cellprototype) + +*** + +### \_columnPrototype? + +```ts +optional _columnPrototype: object; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:135](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L135) + +Prototype cache for Column objects - shared by all columns in this table + +#### Inherited from + +[`Table_CoreProperties`](Table_CoreProperties.md).[`_columnPrototype`](Table_CoreProperties.md#_columnprototype) + +*** + +### \_features + +```ts +readonly _features: Partial & TFeatures; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:139](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L139) + +The features that are enabled for the table. + +#### Inherited from + +[`Table_CoreProperties`](Table_CoreProperties.md).[`_features`](Table_CoreProperties.md#_features) + +*** + +### \_headerPrototype? + +```ts +optional _headerPrototype: object; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:143](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L143) + +Prototype cache for Header objects - shared by all headers in this table + +#### Inherited from + +[`Table_CoreProperties`](Table_CoreProperties.md).[`_headerPrototype`](Table_CoreProperties.md#_headerprototype) + +*** + +### \_reactivity + +```ts +readonly _reactivity: TableReactivityBindings; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:127](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L127) + +Table reactivity bindings for interacting with TanStack Store. + +#### Inherited from + +[`Table_CoreProperties`](Table_CoreProperties.md).[`_reactivity`](Table_CoreProperties.md#_reactivity) + +*** + +### \_rowModelFns + +```ts +readonly _rowModelFns: RowModelFns; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:147](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L147) + +The row model processing functions that are used to process the data by features. + +#### Inherited from + +[`Table_CoreProperties`](Table_CoreProperties.md).[`_rowModelFns`](Table_CoreProperties.md#_rowmodelfns) + +*** + +### \_rowModels + +```ts +readonly _rowModels: CachedRowModels; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:151](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L151) + +The row models that are enabled for the table. + +#### Inherited from + +[`Table_CoreProperties`](Table_CoreProperties.md).[`_rowModels`](Table_CoreProperties.md#_rowmodels) + +*** + +### \_rowPrototype? + +```ts +optional _rowPrototype: object; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:155](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L155) + +Prototype cache for Row objects - shared by all rows in this table + +#### Inherited from + +[`Table_CoreProperties`](Table_CoreProperties.md).[`_rowPrototype`](Table_CoreProperties.md#_rowprototype) + +*** + +### atoms + +```ts +readonly atoms: Atoms; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:161](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L161) + +The readonly derived atoms for each `TableState` slice. Each derives from +its corresponding `baseAtom` plus, optionally, a per-slice external atom or +external state value (precedence: external atom > external state > base atom). + +#### Inherited from + +[`Table_CoreProperties`](Table_CoreProperties.md).[`atoms`](Table_CoreProperties.md#atoms) + +*** + +### baseAtoms + +```ts +readonly baseAtoms: BaseAtoms; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:166](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L166) + +The internal writable atoms for each `TableState` slice. This is the library's +single write surface — all state mutations from features land here. + +#### Inherited from + +[`Table_CoreProperties`](Table_CoreProperties.md).[`baseAtoms`](Table_CoreProperties.md#baseatoms) + +*** + +### initialState + +```ts +readonly initialState: TableState; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:170](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L170) + +This is the resolved initial state of the table. + +#### Inherited from + +[`Table_CoreProperties`](Table_CoreProperties.md).[`initialState`](Table_CoreProperties.md#initialstate) + +*** + +### options + +```ts +readonly options: TableOptions; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:174](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L174) + +A read-only reference to the table's current options. + +#### Inherited from + +[`Table_CoreProperties`](Table_CoreProperties.md).[`options`](Table_CoreProperties.md#options) + +*** + +### optionsStore? + +```ts +readonly optional optionsStore: Atom>; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:180](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L180) + +Writable atom for table options. Only created when `createOptionsStore` is +true on the active core reactivity bindings. Adapters that opt out keep +options as plain resolved data instead of backing them with an atom. + +#### Inherited from + +[`Table_CoreProperties`](Table_CoreProperties.md).[`optionsStore`](Table_CoreProperties.md#optionsstore) + +*** + +### reset() + +```ts +reset: () => void; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:199](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L199) + +Resets the table's internal base atoms to `table.initialState`. + +Prefer feature-specific reset APIs, such as `resetPagination`, when a state +slice may be owned by an external atom or needs that feature's blank/default +reset behavior. + +#### Returns + +`void` + +*** + +### setOptions() + +```ts +setOptions: (newOptions) => void; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:204](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L204) + +Updates the table options by applying a value or updater to the current +resolved options and then merging them through `options.mergeOptions`. + +#### Parameters + +##### newOptions + +[`Updater`](../type-aliases/Updater.md)\<[`TableOptions`](../type-aliases/TableOptions.md)\<`TFeatures`, `TData`\>\> + +#### Returns + +`void` + +*** + +### store + +```ts +readonly store: ReadonlyStore>; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:185](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L185) + +The readonly flat store for the table state. Derives from `table.atoms` +only; never reads external state directly. + +#### Inherited from + +[`Table_CoreProperties`](Table_CoreProperties.md).[`store`](Table_CoreProperties.md#store) diff --git a/docs/reference/index/interfaces/columnResizingState.md b/docs/reference/index/interfaces/columnResizingState.md new file mode 100644 index 0000000000..b38d9badf9 --- /dev/null +++ b/docs/reference/index/interfaces/columnResizingState.md @@ -0,0 +1,68 @@ +--- +id: columnResizingState +title: columnResizingState +--- + +# Interface: columnResizingState + +Defined in: [features/column-resizing/columnResizingFeature.types.ts:7](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts#L7) + +## Properties + +### columnSizingStart + +```ts +columnSizingStart: [string, number][]; +``` + +Defined in: [features/column-resizing/columnResizingFeature.types.ts:8](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts#L8) + +*** + +### deltaOffset + +```ts +deltaOffset: number | null; +``` + +Defined in: [features/column-resizing/columnResizingFeature.types.ts:9](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts#L9) + +*** + +### deltaPercentage + +```ts +deltaPercentage: number | null; +``` + +Defined in: [features/column-resizing/columnResizingFeature.types.ts:10](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts#L10) + +*** + +### isResizingColumn + +```ts +isResizingColumn: string | false; +``` + +Defined in: [features/column-resizing/columnResizingFeature.types.ts:11](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts#L11) + +*** + +### startOffset + +```ts +startOffset: number | null; +``` + +Defined in: [features/column-resizing/columnResizingFeature.types.ts:12](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts#L12) + +*** + +### startSize + +```ts +startSize: number | null; +``` + +Defined in: [features/column-resizing/columnResizingFeature.types.ts:13](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts#L13) diff --git a/docs/reference/index/namespaces/filterFn_arrIncludes/functions/autoRemove.md b/docs/reference/index/namespaces/filterFn_arrIncludes/functions/autoRemove.md new file mode 100644 index 0000000000..e2b8ab07c2 --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_arrIncludes/functions/autoRemove.md @@ -0,0 +1,22 @@ +--- +id: autoRemove +title: autoRemove +--- + +# Function: autoRemove() + +```ts +function autoRemove(val): boolean; +``` + +Defined in: [fns/filterFns.ts:316](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L316) + +## Parameters + +### val + +`any` + +## Returns + +`boolean` diff --git a/docs/reference/index/namespaces/filterFn_arrIncludes/index.md b/docs/reference/index/namespaces/filterFn_arrIncludes/index.md new file mode 100644 index 0000000000..39dbc36047 --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_arrIncludes/index.md @@ -0,0 +1,12 @@ +--- +id: filterFn_arrIncludes +title: filterFn_arrIncludes +--- + +# filterFn\_arrIncludes + +Filter function for checking if an array includes a given value. + +## Functions + +- [autoRemove](functions/autoRemove.md) diff --git a/docs/reference/index/namespaces/filterFn_arrIncludesAll/functions/autoRemove.md b/docs/reference/index/namespaces/filterFn_arrIncludesAll/functions/autoRemove.md new file mode 100644 index 0000000000..6749ac0134 --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_arrIncludesAll/functions/autoRemove.md @@ -0,0 +1,22 @@ +--- +id: autoRemove +title: autoRemove +--- + +# Function: autoRemove() + +```ts +function autoRemove(val): boolean; +``` + +Defined in: [fns/filterFns.ts:334](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L334) + +## Parameters + +### val + +`any` + +## Returns + +`boolean` diff --git a/docs/reference/index/namespaces/filterFn_arrIncludesAll/index.md b/docs/reference/index/namespaces/filterFn_arrIncludesAll/index.md new file mode 100644 index 0000000000..ee2ffb6838 --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_arrIncludesAll/index.md @@ -0,0 +1,12 @@ +--- +id: filterFn_arrIncludesAll +title: filterFn_arrIncludesAll +--- + +# filterFn\_arrIncludesAll + +Filter function for checking if an array includes all of the given values. + +## Functions + +- [autoRemove](functions/autoRemove.md) diff --git a/docs/reference/index/namespaces/filterFn_arrIncludesSome/functions/autoRemove.md b/docs/reference/index/namespaces/filterFn_arrIncludesSome/functions/autoRemove.md new file mode 100644 index 0000000000..5c8129fd1a --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_arrIncludesSome/functions/autoRemove.md @@ -0,0 +1,22 @@ +--- +id: autoRemove +title: autoRemove +--- + +# Function: autoRemove() + +```ts +function autoRemove(val): boolean; +``` + +Defined in: [fns/filterFns.ts:353](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L353) + +## Parameters + +### val + +`any` + +## Returns + +`boolean` diff --git a/docs/reference/index/namespaces/filterFn_arrIncludesSome/index.md b/docs/reference/index/namespaces/filterFn_arrIncludesSome/index.md new file mode 100644 index 0000000000..6e7440d236 --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_arrIncludesSome/index.md @@ -0,0 +1,12 @@ +--- +id: filterFn_arrIncludesSome +title: filterFn_arrIncludesSome +--- + +# filterFn\_arrIncludesSome + +Filter function for checking if an array includes any of the given values. + +## Functions + +- [autoRemove](functions/autoRemove.md) diff --git a/docs/reference/index/namespaces/filterFn_equals/functions/autoRemove.md b/docs/reference/index/namespaces/filterFn_equals/functions/autoRemove.md new file mode 100644 index 0000000000..4215c8844e --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_equals/functions/autoRemove.md @@ -0,0 +1,22 @@ +--- +id: autoRemove +title: autoRemove +--- + +# Function: autoRemove() + +```ts +function autoRemove(val): boolean; +``` + +Defined in: [fns/filterFns.ts:22](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L22) + +## Parameters + +### val + +`any` + +## Returns + +`boolean` diff --git a/docs/reference/index/namespaces/filterFn_equals/index.md b/docs/reference/index/namespaces/filterFn_equals/index.md new file mode 100644 index 0000000000..a84b3749fa --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_equals/index.md @@ -0,0 +1,12 @@ +--- +id: filterFn_equals +title: filterFn_equals +--- + +# filterFn\_equals + +Filter function for checking if a value is exactly equal to a given value. (JS === comparison) + +## Functions + +- [autoRemove](functions/autoRemove.md) diff --git a/docs/reference/index/namespaces/filterFn_equalsString/functions/autoRemove.md b/docs/reference/index/namespaces/filterFn_equalsString/functions/autoRemove.md new file mode 100644 index 0000000000..3007b1e7db --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_equalsString/functions/autoRemove.md @@ -0,0 +1,22 @@ +--- +id: autoRemove +title: autoRemove +--- + +# Function: autoRemove() + +```ts +function autoRemove(val): boolean; +``` + +Defined in: [fns/filterFns.ts:99](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L99) + +## Parameters + +### val + +`any` + +## Returns + +`boolean` diff --git a/docs/reference/index/namespaces/filterFn_equalsString/index.md b/docs/reference/index/namespaces/filterFn_equalsString/index.md new file mode 100644 index 0000000000..f1b1984393 --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_equalsString/index.md @@ -0,0 +1,12 @@ +--- +id: filterFn_equalsString +title: filterFn_equalsString +--- + +# filterFn\_equalsString + +Filter function for checking if a string is exactly equal to a given string. (Non-case-sensitive) + +## Functions + +- [autoRemove](functions/autoRemove.md) diff --git a/docs/reference/index/namespaces/filterFn_equalsStringSensitive/functions/autoRemove.md b/docs/reference/index/namespaces/filterFn_equalsStringSensitive/functions/autoRemove.md new file mode 100644 index 0000000000..8700832f1e --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_equalsStringSensitive/functions/autoRemove.md @@ -0,0 +1,22 @@ +--- +id: autoRemove +title: autoRemove +--- + +# Function: autoRemove() + +```ts +function autoRemove(val): boolean; +``` + +Defined in: [fns/filterFns.ts:115](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L115) + +## Parameters + +### val + +`any` + +## Returns + +`boolean` diff --git a/docs/reference/index/namespaces/filterFn_equalsStringSensitive/index.md b/docs/reference/index/namespaces/filterFn_equalsStringSensitive/index.md new file mode 100644 index 0000000000..bf99c06b65 --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_equalsStringSensitive/index.md @@ -0,0 +1,12 @@ +--- +id: filterFn_equalsStringSensitive +title: filterFn_equalsStringSensitive +--- + +# filterFn\_equalsStringSensitive + +Filter function for checking if a string is exactly equal to a given string. (Case-sensitive) + +## Functions + +- [autoRemove](functions/autoRemove.md) diff --git a/docs/reference/index/namespaces/filterFn_greaterThan/functions/resolveFilterValue.md b/docs/reference/index/namespaces/filterFn_greaterThan/functions/resolveFilterValue.md new file mode 100644 index 0000000000..e54f30b5cc --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_greaterThan/functions/resolveFilterValue.md @@ -0,0 +1,22 @@ +--- +id: resolveFilterValue +title: resolveFilterValue +--- + +# Function: resolveFilterValue() + +```ts +function resolveFilterValue(val): boolean; +``` + +Defined in: [fns/filterFns.ts:144](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L144) + +## Parameters + +### val + +`any` + +## Returns + +`boolean` diff --git a/docs/reference/index/namespaces/filterFn_greaterThan/index.md b/docs/reference/index/namespaces/filterFn_greaterThan/index.md new file mode 100644 index 0000000000..12ec8a7afa --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_greaterThan/index.md @@ -0,0 +1,12 @@ +--- +id: filterFn_greaterThan +title: filterFn_greaterThan +--- + +# filterFn\_greaterThan + +Filter function for checking if a number is greater than a given number. + +## Functions + +- [resolveFilterValue](functions/resolveFilterValue.md) diff --git a/docs/reference/index/namespaces/filterFn_greaterThanOrEqualTo/functions/resolveFilterValue.md b/docs/reference/index/namespaces/filterFn_greaterThanOrEqualTo/functions/resolveFilterValue.md new file mode 100644 index 0000000000..26c4350672 --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_greaterThanOrEqualTo/functions/resolveFilterValue.md @@ -0,0 +1,22 @@ +--- +id: resolveFilterValue +title: resolveFilterValue +--- + +# Function: resolveFilterValue() + +```ts +function resolveFilterValue(val): boolean; +``` + +Defined in: [fns/filterFns.ts:163](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L163) + +## Parameters + +### val + +`any` + +## Returns + +`boolean` diff --git a/docs/reference/index/namespaces/filterFn_greaterThanOrEqualTo/index.md b/docs/reference/index/namespaces/filterFn_greaterThanOrEqualTo/index.md new file mode 100644 index 0000000000..333b43fbda --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_greaterThanOrEqualTo/index.md @@ -0,0 +1,12 @@ +--- +id: filterFn_greaterThanOrEqualTo +title: filterFn_greaterThanOrEqualTo +--- + +# filterFn\_greaterThanOrEqualTo + +Filter function for checking if a number is greater than or equal to a given number. + +## Functions + +- [resolveFilterValue](functions/resolveFilterValue.md) diff --git a/docs/reference/index/namespaces/filterFn_inNumberRange/functions/autoRemove.md b/docs/reference/index/namespaces/filterFn_inNumberRange/functions/autoRemove.md new file mode 100644 index 0000000000..326e90605f --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_inNumberRange/functions/autoRemove.md @@ -0,0 +1,22 @@ +--- +id: autoRemove +title: autoRemove +--- + +# Function: autoRemove() + +```ts +function autoRemove(val): boolean; +``` + +Defined in: [fns/filterFns.ts:279](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L279) + +## Parameters + +### val + +`any` + +## Returns + +`boolean` diff --git a/docs/reference/index/namespaces/filterFn_inNumberRange/functions/resolveFilterValue.md b/docs/reference/index/namespaces/filterFn_inNumberRange/functions/resolveFilterValue.md new file mode 100644 index 0000000000..24e9ffb513 --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_inNumberRange/functions/resolveFilterValue.md @@ -0,0 +1,22 @@ +--- +id: resolveFilterValue +title: resolveFilterValue +--- + +# Function: resolveFilterValue() + +```ts +function resolveFilterValue(val): readonly [number, number]; +``` + +Defined in: [fns/filterFns.ts:258](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L258) + +## Parameters + +### val + +\[`any`, `any`\] + +## Returns + +readonly \[`number`, `number`\] diff --git a/docs/reference/index/namespaces/filterFn_inNumberRange/index.md b/docs/reference/index/namespaces/filterFn_inNumberRange/index.md new file mode 100644 index 0000000000..0336d66395 --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_inNumberRange/index.md @@ -0,0 +1,13 @@ +--- +id: filterFn_inNumberRange +title: filterFn_inNumberRange +--- + +# filterFn\_inNumberRange + +Filter function for checking if a number is within a given range. + +## Functions + +- [autoRemove](functions/autoRemove.md) +- [resolveFilterValue](functions/resolveFilterValue.md) diff --git a/docs/reference/index/namespaces/filterFn_includesString/functions/autoRemove.md b/docs/reference/index/namespaces/filterFn_includesString/functions/autoRemove.md new file mode 100644 index 0000000000..40bcbd6dd1 --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_includesString/functions/autoRemove.md @@ -0,0 +1,22 @@ +--- +id: autoRemove +title: autoRemove +--- + +# Function: autoRemove() + +```ts +function autoRemove(val): boolean; +``` + +Defined in: [fns/filterFns.ts:80](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L80) + +## Parameters + +### val + +`any` + +## Returns + +`boolean` diff --git a/docs/reference/index/namespaces/filterFn_includesString/index.md b/docs/reference/index/namespaces/filterFn_includesString/index.md new file mode 100644 index 0000000000..04612a30bb --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_includesString/index.md @@ -0,0 +1,12 @@ +--- +id: filterFn_includesString +title: filterFn_includesString +--- + +# filterFn\_includesString + +Filter function for checking if a string includes a given substring. (Non-case-sensitive) + +## Functions + +- [autoRemove](functions/autoRemove.md) diff --git a/docs/reference/index/namespaces/filterFn_includesStringSensitive/functions/autoRemove.md b/docs/reference/index/namespaces/filterFn_includesStringSensitive/functions/autoRemove.md new file mode 100644 index 0000000000..2a3cce59e9 --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_includesStringSensitive/functions/autoRemove.md @@ -0,0 +1,22 @@ +--- +id: autoRemove +title: autoRemove +--- + +# Function: autoRemove() + +```ts +function autoRemove(val): boolean; +``` + +Defined in: [fns/filterFns.ts:58](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L58) + +## Parameters + +### val + +`any` + +## Returns + +`boolean` diff --git a/docs/reference/index/namespaces/filterFn_includesStringSensitive/index.md b/docs/reference/index/namespaces/filterFn_includesStringSensitive/index.md new file mode 100644 index 0000000000..93c0e855a3 --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_includesStringSensitive/index.md @@ -0,0 +1,12 @@ +--- +id: filterFn_includesStringSensitive +title: filterFn_includesStringSensitive +--- + +# filterFn\_includesStringSensitive + +Filter function for checking if a string includes a given substring. (Case-sensitive) + +## Functions + +- [autoRemove](functions/autoRemove.md) diff --git a/docs/reference/index/namespaces/filterFn_lessThan/functions/resolveFilterValue.md b/docs/reference/index/namespaces/filterFn_lessThan/functions/resolveFilterValue.md new file mode 100644 index 0000000000..d06754949d --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_lessThan/functions/resolveFilterValue.md @@ -0,0 +1,22 @@ +--- +id: resolveFilterValue +title: resolveFilterValue +--- + +# Function: resolveFilterValue() + +```ts +function resolveFilterValue(val): boolean; +``` + +Defined in: [fns/filterFns.ts:179](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L179) + +## Parameters + +### val + +`any` + +## Returns + +`boolean` diff --git a/docs/reference/index/namespaces/filterFn_lessThan/index.md b/docs/reference/index/namespaces/filterFn_lessThan/index.md new file mode 100644 index 0000000000..764110d19a --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_lessThan/index.md @@ -0,0 +1,12 @@ +--- +id: filterFn_lessThan +title: filterFn_lessThan +--- + +# filterFn\_lessThan + +Filter function for checking if a number is less than a given number. + +## Functions + +- [resolveFilterValue](functions/resolveFilterValue.md) diff --git a/docs/reference/index/namespaces/filterFn_lessThanOrEqualTo/functions/resolveFilterValue.md b/docs/reference/index/namespaces/filterFn_lessThanOrEqualTo/functions/resolveFilterValue.md new file mode 100644 index 0000000000..c603b9a79a --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_lessThanOrEqualTo/functions/resolveFilterValue.md @@ -0,0 +1,22 @@ +--- +id: resolveFilterValue +title: resolveFilterValue +--- + +# Function: resolveFilterValue() + +```ts +function resolveFilterValue(val): boolean; +``` + +Defined in: [fns/filterFns.ts:195](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L195) + +## Parameters + +### val + +`any` + +## Returns + +`boolean` diff --git a/docs/reference/index/namespaces/filterFn_lessThanOrEqualTo/index.md b/docs/reference/index/namespaces/filterFn_lessThanOrEqualTo/index.md new file mode 100644 index 0000000000..ffa6e3e500 --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_lessThanOrEqualTo/index.md @@ -0,0 +1,12 @@ +--- +id: filterFn_lessThanOrEqualTo +title: filterFn_lessThanOrEqualTo +--- + +# filterFn\_lessThanOrEqualTo + +Filter function for checking if a number is less than or equal to a given number. + +## Functions + +- [resolveFilterValue](functions/resolveFilterValue.md) diff --git a/docs/reference/index/namespaces/filterFn_weakEquals/functions/autoRemove.md b/docs/reference/index/namespaces/filterFn_weakEquals/functions/autoRemove.md new file mode 100644 index 0000000000..dfd81b6bc8 --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_weakEquals/functions/autoRemove.md @@ -0,0 +1,22 @@ +--- +id: autoRemove +title: autoRemove +--- + +# Function: autoRemove() + +```ts +function autoRemove(val): boolean; +``` + +Defined in: [fns/filterFns.ts:38](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L38) + +## Parameters + +### val + +`any` + +## Returns + +`boolean` diff --git a/docs/reference/index/namespaces/filterFn_weakEquals/index.md b/docs/reference/index/namespaces/filterFn_weakEquals/index.md new file mode 100644 index 0000000000..658aea77bf --- /dev/null +++ b/docs/reference/index/namespaces/filterFn_weakEquals/index.md @@ -0,0 +1,12 @@ +--- +id: filterFn_weakEquals +title: filterFn_weakEquals +--- + +# filterFn\_weakEquals + +Filter function for checking if a value is weakly equal to a given value. (JS == comparison) + +## Functions + +- [autoRemove](functions/autoRemove.md) diff --git a/docs/reference/index/type-aliases/APIObject.md b/docs/reference/index/type-aliases/APIObject.md new file mode 100644 index 0000000000..a7cf64ba72 --- /dev/null +++ b/docs/reference/index/type-aliases/APIObject.md @@ -0,0 +1,22 @@ +--- +id: APIObject +title: APIObject +--- + +# Type Alias: APIObject\ + +```ts +type APIObject = Record>; +``` + +Defined in: [utils.ts:336](https://github.com/TanStack/table/blob/main/packages/table-core/src/utils.ts#L336) + +## Type Parameters + +### TDeps + +`TDeps` *extends* `ReadonlyArray`\<`any`\> + +### TDepArgs + +`TDepArgs` diff --git a/docs/reference/index/type-aliases/AccessorColumnDef.md b/docs/reference/index/type-aliases/AccessorColumnDef.md new file mode 100644 index 0000000000..2aef75707b --- /dev/null +++ b/docs/reference/index/type-aliases/AccessorColumnDef.md @@ -0,0 +1,28 @@ +--- +id: AccessorColumnDef +title: AccessorColumnDef +--- + +# Type Alias: AccessorColumnDef\ + +```ts +type AccessorColumnDef = + | AccessorKeyColumnDef +| AccessorFnColumnDef; +``` + +Defined in: [types/ColumnDef.ts:194](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L194) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](CellData.md) = [`CellData`](CellData.md) diff --git a/docs/reference/index/type-aliases/AccessorFn.md b/docs/reference/index/type-aliases/AccessorFn.md new file mode 100644 index 0000000000..c66ac0af96 --- /dev/null +++ b/docs/reference/index/type-aliases/AccessorFn.md @@ -0,0 +1,36 @@ +--- +id: AccessorFn +title: AccessorFn +--- + +# Type Alias: AccessorFn()\ + +```ts +type AccessorFn = (originalRow, index) => TValue; +``` + +Defined in: [types/ColumnDef.ts:30](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L30) + +## Type Parameters + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](CellData.md) = [`CellData`](CellData.md) + +## Parameters + +### originalRow + +`TData` + +### index + +`number` + +## Returns + +`TValue` diff --git a/docs/reference/index/type-aliases/AccessorFnColumnDef.md b/docs/reference/index/type-aliases/AccessorFnColumnDef.md new file mode 100644 index 0000000000..bb77da4e78 --- /dev/null +++ b/docs/reference/index/type-aliases/AccessorFnColumnDef.md @@ -0,0 +1,26 @@ +--- +id: AccessorFnColumnDef +title: AccessorFnColumnDef +--- + +# Type Alias: AccessorFnColumnDef\ + +```ts +type AccessorFnColumnDef = AccessorFnColumnDefBase & ColumnIdentifiers; +``` + +Defined in: [types/ColumnDef.ts:171](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L171) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](CellData.md) = [`CellData`](CellData.md) diff --git a/docs/reference/index/type-aliases/AccessorFnColumnDefBase.md b/docs/reference/index/type-aliases/AccessorFnColumnDefBase.md new file mode 100644 index 0000000000..0949ba3710 --- /dev/null +++ b/docs/reference/index/type-aliases/AccessorFnColumnDefBase.md @@ -0,0 +1,34 @@ +--- +id: AccessorFnColumnDefBase +title: AccessorFnColumnDefBase +--- + +# Type Alias: AccessorFnColumnDefBase\ + +```ts +type AccessorFnColumnDefBase = ColumnDefBase & object; +``` + +Defined in: [types/ColumnDef.ts:163](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L163) + +## Type Declaration + +### accessorFn + +```ts +accessorFn: AccessorFn; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](CellData.md) = [`CellData`](CellData.md) diff --git a/docs/reference/index/type-aliases/AccessorKeyColumnDef.md b/docs/reference/index/type-aliases/AccessorKeyColumnDef.md new file mode 100644 index 0000000000..c60f4368cd --- /dev/null +++ b/docs/reference/index/type-aliases/AccessorKeyColumnDef.md @@ -0,0 +1,26 @@ +--- +id: AccessorKeyColumnDef +title: AccessorKeyColumnDef +--- + +# Type Alias: AccessorKeyColumnDef\ + +```ts +type AccessorKeyColumnDef = AccessorKeyColumnDefBase & Partial>; +``` + +Defined in: [types/ColumnDef.ts:187](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L187) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](CellData.md) = [`CellData`](CellData.md) diff --git a/docs/reference/index/type-aliases/AccessorKeyColumnDefBase.md b/docs/reference/index/type-aliases/AccessorKeyColumnDefBase.md new file mode 100644 index 0000000000..8a180c439f --- /dev/null +++ b/docs/reference/index/type-aliases/AccessorKeyColumnDefBase.md @@ -0,0 +1,40 @@ +--- +id: AccessorKeyColumnDefBase +title: AccessorKeyColumnDefBase +--- + +# Type Alias: AccessorKeyColumnDefBase\ + +```ts +type AccessorKeyColumnDefBase = ColumnDefBase & object; +``` + +Defined in: [types/ColumnDef.ts:178](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L178) + +## Type Declaration + +### accessorKey + +```ts +accessorKey: string & object | keyof TData; +``` + +### id? + +```ts +optional id: string; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](CellData.md) = [`CellData`](CellData.md) diff --git a/docs/reference/index/type-aliases/AggregationFn.md b/docs/reference/index/type-aliases/AggregationFn.md new file mode 100644 index 0000000000..4b6026e99a --- /dev/null +++ b/docs/reference/index/type-aliases/AggregationFn.md @@ -0,0 +1,40 @@ +--- +id: AggregationFn +title: AggregationFn +--- + +# Type Alias: AggregationFn()\ + +```ts +type AggregationFn = (columnId, leafRows, childRows) => any; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:30](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L30) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +## Parameters + +### columnId + +`string` + +### leafRows + +[`Row`](Row.md)\<`TFeatures`, `TData`\>[] + +### childRows + +[`Row`](Row.md)\<`TFeatures`, `TData`\>[] + +## Returns + +`any` diff --git a/docs/reference/index/type-aliases/AggregationFnOption.md b/docs/reference/index/type-aliases/AggregationFnOption.md new file mode 100644 index 0000000000..e769ff8947 --- /dev/null +++ b/docs/reference/index/type-aliases/AggregationFnOption.md @@ -0,0 +1,26 @@ +--- +id: AggregationFnOption +title: AggregationFnOption +--- + +# Type Alias: AggregationFnOption\ + +```ts +type AggregationFnOption = + | "auto" + | keyof AggregationFns + | BuiltInAggregationFn +| AggregationFn; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:44](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L44) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) diff --git a/docs/reference/index/type-aliases/AssignCellPrototype.md b/docs/reference/index/type-aliases/AssignCellPrototype.md new file mode 100644 index 0000000000..181f147bee --- /dev/null +++ b/docs/reference/index/type-aliases/AssignCellPrototype.md @@ -0,0 +1,42 @@ +--- +id: AssignCellPrototype +title: AssignCellPrototype +--- + +# Type Alias: AssignCellPrototype()\ + +```ts +type AssignCellPrototype = (prototype, table) => void; +``` + +Defined in: [types/TableFeatures.ts:77](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L77) + +## Type Parameters + +### TConstructors + +`TConstructors` *extends* [`FeatureConstructors`](../interfaces/FeatureConstructors.md) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +## Parameters + +### prototype + +`Record`\<`string`, `any`\> + +### table + +[`Table_Internal`](Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`void` diff --git a/docs/reference/index/type-aliases/AssignColumnPrototype.md b/docs/reference/index/type-aliases/AssignColumnPrototype.md new file mode 100644 index 0000000000..2e53e30e2b --- /dev/null +++ b/docs/reference/index/type-aliases/AssignColumnPrototype.md @@ -0,0 +1,42 @@ +--- +id: AssignColumnPrototype +title: AssignColumnPrototype +--- + +# Type Alias: AssignColumnPrototype()\ + +```ts +type AssignColumnPrototype = (prototype, table) => void; +``` + +Defined in: [types/TableFeatures.ts:85](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L85) + +## Type Parameters + +### TConstructors + +`TConstructors` *extends* [`FeatureConstructors`](../interfaces/FeatureConstructors.md) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +## Parameters + +### prototype + +`Record`\<`string`, `any`\> + +### table + +[`Table_Internal`](Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`void` diff --git a/docs/reference/index/type-aliases/AssignHeaderPrototype.md b/docs/reference/index/type-aliases/AssignHeaderPrototype.md new file mode 100644 index 0000000000..df90118eaa --- /dev/null +++ b/docs/reference/index/type-aliases/AssignHeaderPrototype.md @@ -0,0 +1,42 @@ +--- +id: AssignHeaderPrototype +title: AssignHeaderPrototype +--- + +# Type Alias: AssignHeaderPrototype()\ + +```ts +type AssignHeaderPrototype = (prototype, table) => void; +``` + +Defined in: [types/TableFeatures.ts:93](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L93) + +## Type Parameters + +### TConstructors + +`TConstructors` *extends* [`FeatureConstructors`](../interfaces/FeatureConstructors.md) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +## Parameters + +### prototype + +`Record`\<`string`, `any`\> + +### table + +[`Table_Internal`](Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`void` diff --git a/docs/reference/index/type-aliases/AssignRowPrototype.md b/docs/reference/index/type-aliases/AssignRowPrototype.md new file mode 100644 index 0000000000..59035b7770 --- /dev/null +++ b/docs/reference/index/type-aliases/AssignRowPrototype.md @@ -0,0 +1,42 @@ +--- +id: AssignRowPrototype +title: AssignRowPrototype +--- + +# Type Alias: AssignRowPrototype()\ + +```ts +type AssignRowPrototype = (prototype, table) => void; +``` + +Defined in: [types/TableFeatures.ts:101](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L101) + +## Type Parameters + +### TConstructors + +`TConstructors` *extends* [`FeatureConstructors`](../interfaces/FeatureConstructors.md) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +## Parameters + +### prototype + +`Record`\<`string`, `any`\> + +### table + +[`Table_Internal`](Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`void` diff --git a/docs/reference/index/type-aliases/Atoms.md b/docs/reference/index/type-aliases/Atoms.md new file mode 100644 index 0000000000..9da5ac26dc --- /dev/null +++ b/docs/reference/index/type-aliases/Atoms.md @@ -0,0 +1,24 @@ +--- +id: Atoms +title: Atoms +--- + +# Type Alias: Atoms\ + +```ts +type Atoms = { [K in keyof TableState]-?: ReadonlyAtom[K]> }; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:31](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L31) + +A map of readonly derived atoms, one per `TableState` slice. Each derives +from its corresponding `baseAtom` plus, optionally, a per-slice external +atom or external state value. + +Precedence: `options.atoms[key]` > `options.state[key]` > `baseAtoms[key]`. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) diff --git a/docs/reference/index/type-aliases/Atoms_All.md b/docs/reference/index/type-aliases/Atoms_All.md new file mode 100644 index 0000000000..a37531b870 --- /dev/null +++ b/docs/reference/index/type-aliases/Atoms_All.md @@ -0,0 +1,12 @@ +--- +id: Atoms_All +title: Atoms_All +--- + +# Type Alias: Atoms\_All + +```ts +type Atoms_All = { [K in keyof TableState_All]?: ReadonlyAtom }; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:57](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L57) diff --git a/docs/reference/index/type-aliases/BaseAtoms.md b/docs/reference/index/type-aliases/BaseAtoms.md new file mode 100644 index 0000000000..4afcd76bf4 --- /dev/null +++ b/docs/reference/index/type-aliases/BaseAtoms.md @@ -0,0 +1,21 @@ +--- +id: BaseAtoms +title: BaseAtoms +--- + +# Type Alias: BaseAtoms\ + +```ts +type BaseAtoms = { [K in keyof TableState]-?: Atom[K]> }; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L20) + +A map of writable atoms, one per `TableState` slice. These are the internal +writable atoms that the library always writes to via `makeStateUpdater`. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) diff --git a/docs/reference/index/type-aliases/BaseAtoms_All.md b/docs/reference/index/type-aliases/BaseAtoms_All.md new file mode 100644 index 0000000000..54deb43741 --- /dev/null +++ b/docs/reference/index/type-aliases/BaseAtoms_All.md @@ -0,0 +1,21 @@ +--- +id: BaseAtoms_All +title: BaseAtoms_All +--- + +# Type Alias: BaseAtoms\_All + +```ts +type BaseAtoms_All = { [K in keyof TableState_All]?: Atom }; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:54](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L54) + +Internal "all features" flat variants of the atom types. `Table_Internal` +uses these so feature code (written generically over `TFeatures`) can access +any slice atom (e.g. `table.atoms.columnPinning`) without TypeScript +narrowing away slices that aren't in the current `TFeatures` union. + +Keys are optional: feature code can read atoms from slices it doesn't own, +but those slices may not be registered on the current table. Consumers must +use optional chaining (`table.atoms.columnPinning?.get() ?? default`). diff --git a/docs/reference/index/type-aliases/BuiltInAggregationFn.md b/docs/reference/index/type-aliases/BuiltInAggregationFn.md new file mode 100644 index 0000000000..6ea1c52e5a --- /dev/null +++ b/docs/reference/index/type-aliases/BuiltInAggregationFn.md @@ -0,0 +1,12 @@ +--- +id: BuiltInAggregationFn +title: BuiltInAggregationFn +--- + +# Type Alias: BuiltInAggregationFn + +```ts +type BuiltInAggregationFn = keyof typeof aggregationFns; +``` + +Defined in: [fns/aggregationFns.ts:225](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/aggregationFns.ts#L225) diff --git a/docs/reference/index/type-aliases/BuiltInFilterFn.md b/docs/reference/index/type-aliases/BuiltInFilterFn.md new file mode 100644 index 0000000000..d11b3a6c28 --- /dev/null +++ b/docs/reference/index/type-aliases/BuiltInFilterFn.md @@ -0,0 +1,12 @@ +--- +id: BuiltInFilterFn +title: BuiltInFilterFn +--- + +# Type Alias: BuiltInFilterFn + +```ts +type BuiltInFilterFn = keyof typeof filterFns; +``` + +Defined in: [fns/filterFns.ts:378](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L378) diff --git a/docs/reference/index/type-aliases/BuiltInSortFn.md b/docs/reference/index/type-aliases/BuiltInSortFn.md new file mode 100644 index 0000000000..f067b35ded --- /dev/null +++ b/docs/reference/index/type-aliases/BuiltInSortFn.md @@ -0,0 +1,12 @@ +--- +id: BuiltInSortFn +title: BuiltInSortFn +--- + +# Type Alias: BuiltInSortFn + +```ts +type BuiltInSortFn = keyof typeof sortFns; +``` + +Defined in: [fns/sortFns.ts:214](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/sortFns.ts#L214) diff --git a/docs/reference/index/type-aliases/CachedRowModel_All.md b/docs/reference/index/type-aliases/CachedRowModel_All.md new file mode 100644 index 0000000000..7c88b96722 --- /dev/null +++ b/docs/reference/index/type-aliases/CachedRowModel_All.md @@ -0,0 +1,22 @@ +--- +id: CachedRowModel_All +title: CachedRowModel_All +--- + +# Type Alias: CachedRowModel\_All\ + +```ts +type CachedRowModel_All = Partial & CachedRowModel_Expanded & CachedRowModel_Faceted & CachedRowModel_Filtered & CachedRowModel_Grouped & CachedRowModel_Paginated & CachedRowModel_Sorted>; +``` + +Defined in: [types/RowModel.ts:126](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/RowModel.ts#L126) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) = `any` diff --git a/docs/reference/index/type-aliases/CachedRowModels.md b/docs/reference/index/type-aliases/CachedRowModels.md new file mode 100644 index 0000000000..346ef4da2f --- /dev/null +++ b/docs/reference/index/type-aliases/CachedRowModels.md @@ -0,0 +1,40 @@ +--- +id: CachedRowModels +title: CachedRowModels +--- + +# Type Alias: CachedRowModels\ + +```ts +type CachedRowModels = object & UnionToIntersection< + | "columnFacetingFeature" extends keyof TFeatures ? CachedRowModel_Faceted : never + | "columnFilteringFeature" extends keyof TFeatures ? CachedRowModel_Filtered : never + | "rowExpandingFeature" extends keyof TFeatures ? CachedRowModel_Expanded : never + | "columnGroupingFeature" extends keyof TFeatures ? CachedRowModel_Grouped : never + | "rowPaginationFeature" extends keyof TFeatures ? CachedRowModel_Paginated : never +| "rowSortingFeature" extends keyof TFeatures ? CachedRowModel_Sorted : never> & ExtractFeatureTypes<"CachedRowModel", TFeatures> & CachedRowModels_Plugins; +``` + +Defined in: [types/RowModel.ts:92](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/RowModel.ts#L92) + +## Type Declaration + +### CachedRowModel\_Core() + +```ts +CachedRowModel_Core: () => RowModel; +``` + +#### Returns + +[`RowModel`](../interfaces/RowModel.md)\<`TFeatures`, `TData`\> + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) diff --git a/docs/reference/index/type-aliases/Cell.md b/docs/reference/index/type-aliases/Cell.md new file mode 100644 index 0000000000..d502b45063 --- /dev/null +++ b/docs/reference/index/type-aliases/Cell.md @@ -0,0 +1,26 @@ +--- +id: Cell +title: Cell +--- + +# Type Alias: Cell\ + +```ts +type Cell = Cell_Cell & UnionToIntersection<"columnGroupingFeature" extends keyof TFeatures ? Cell_ColumnGrouping : never> & ExtractFeatureTypes<"Cell", TFeatures> & Cell_Plugins; +``` + +Defined in: [types/Cell.ts:22](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Cell.ts#L22) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](CellData.md) = [`CellData`](CellData.md) diff --git a/docs/reference/index/type-aliases/CellData.md b/docs/reference/index/type-aliases/CellData.md new file mode 100644 index 0000000000..b87bbfc760 --- /dev/null +++ b/docs/reference/index/type-aliases/CellData.md @@ -0,0 +1,12 @@ +--- +id: CellData +title: CellData +--- + +# Type Alias: CellData + +```ts +type CellData = unknown; +``` + +Defined in: [types/type-utils.ts:7](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/type-utils.ts#L7) diff --git a/docs/reference/index/type-aliases/Column.md b/docs/reference/index/type-aliases/Column.md new file mode 100644 index 0000000000..37c74859af --- /dev/null +++ b/docs/reference/index/type-aliases/Column.md @@ -0,0 +1,36 @@ +--- +id: Column +title: Column +--- + +# Type Alias: Column\ + +```ts +type Column = Column_Core & UnionToIntersection< + | "columnFacetingFeature" extends keyof TFeatures ? Column_ColumnFaceting : never + | "columnFilteringFeature" extends keyof TFeatures ? Column_ColumnFiltering : never + | "columnGroupingFeature" extends keyof TFeatures ? Column_ColumnGrouping : never + | "columnOrderingFeature" extends keyof TFeatures ? Column_ColumnOrdering : never + | "columnPinningFeature" extends keyof TFeatures ? Column_ColumnPinning : never + | "columnResizingFeature" extends keyof TFeatures ? Column_ColumnResizing : never + | "columnSizingFeature" extends keyof TFeatures ? Column_ColumnSizing : never + | "columnVisibilityFeature" extends keyof TFeatures ? Column_ColumnVisibility : never + | "globalFilteringFeature" extends keyof TFeatures ? Column_GlobalFiltering : never +| "rowSortingFeature" extends keyof TFeatures ? Column_RowSorting : never> & ExtractFeatureTypes<"Column", TFeatures> & Column_Plugins; +``` + +Defined in: [types/Column.ts:32](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L32) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +### TValue + +`TValue` = `unknown` diff --git a/docs/reference/index/type-aliases/ColumnDef.md b/docs/reference/index/type-aliases/ColumnDef.md new file mode 100644 index 0000000000..3afd463a1c --- /dev/null +++ b/docs/reference/index/type-aliases/ColumnDef.md @@ -0,0 +1,29 @@ +--- +id: ColumnDef +title: ColumnDef +--- + +# Type Alias: ColumnDef\ + +```ts +type ColumnDef = + | DisplayColumnDef + | GroupColumnDef +| AccessorColumnDef; +``` + +Defined in: [types/ColumnDef.ts:202](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L202) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](CellData.md) = [`CellData`](CellData.md) diff --git a/docs/reference/index/type-aliases/ColumnDefBase.md b/docs/reference/index/type-aliases/ColumnDefBase.md new file mode 100644 index 0000000000..1c291b5ff8 --- /dev/null +++ b/docs/reference/index/type-aliases/ColumnDefBase.md @@ -0,0 +1,34 @@ +--- +id: ColumnDefBase +title: ColumnDefBase +--- + +# Type Alias: ColumnDefBase\ + +```ts +type ColumnDefBase = ColumnDefBase_Core & UnionToIntersection< + | "columnVisibilityFeature" extends keyof TFeatures ? ColumnDef_ColumnVisibility : never + | "columnPinningFeature" extends keyof TFeatures ? ColumnDef_ColumnPinning : never + | "columnFilteringFeature" extends keyof TFeatures ? ColumnDef_ColumnFiltering : never + | "globalFilteringFeature" extends keyof TFeatures ? ColumnDef_GlobalFiltering : never + | "rowSortingFeature" extends keyof TFeatures ? ColumnDef_RowSorting : never + | "columnGroupingFeature" extends keyof TFeatures ? ColumnDef_ColumnGrouping : never + | "columnSizingFeature" extends keyof TFeatures ? ColumnDef_ColumnSizing : never +| "columnResizingFeature" extends keyof TFeatures ? ColumnDef_ColumnResizing : never> & ExtractFeatureTypes<"ColumnDef", TFeatures> & ColumnDef_Plugins; +``` + +Defined in: [types/ColumnDef.ts:75](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L75) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](CellData.md) = [`CellData`](CellData.md) diff --git a/docs/reference/index/type-aliases/ColumnDefBase_All.md b/docs/reference/index/type-aliases/ColumnDefBase_All.md new file mode 100644 index 0000000000..b1428ac707 --- /dev/null +++ b/docs/reference/index/type-aliases/ColumnDefBase_All.md @@ -0,0 +1,26 @@ +--- +id: ColumnDefBase_All +title: ColumnDefBase_All +--- + +# Type Alias: ColumnDefBase\_All\ + +```ts +type ColumnDefBase_All = ColumnDefBase_Core & Partial & ColumnDef_GlobalFiltering & ColumnDef_RowSorting & ColumnDef_ColumnGrouping & ColumnDef_ColumnSizing & ColumnDef_ColumnResizing>; +``` + +Defined in: [types/ColumnDef.ts:117](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L117) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](CellData.md) = [`CellData`](CellData.md) diff --git a/docs/reference/index/type-aliases/ColumnDefResolved.md b/docs/reference/index/type-aliases/ColumnDefResolved.md new file mode 100644 index 0000000000..f475f890a7 --- /dev/null +++ b/docs/reference/index/type-aliases/ColumnDefResolved.md @@ -0,0 +1,34 @@ +--- +id: ColumnDefResolved +title: ColumnDefResolved +--- + +# Type Alias: ColumnDefResolved\ + +```ts +type ColumnDefResolved = Partial>> & object; +``` + +Defined in: [types/ColumnDef.ts:211](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L211) + +## Type Declaration + +### accessorKey? + +```ts +optional accessorKey: string; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](CellData.md) = [`CellData`](CellData.md) diff --git a/docs/reference/index/type-aliases/ColumnDefTemplate.md b/docs/reference/index/type-aliases/ColumnDefTemplate.md new file mode 100644 index 0000000000..2f3f105ce0 --- /dev/null +++ b/docs/reference/index/type-aliases/ColumnDefTemplate.md @@ -0,0 +1,18 @@ +--- +id: ColumnDefTemplate +title: ColumnDefTemplate +--- + +# Type Alias: ColumnDefTemplate\ + +```ts +type ColumnDefTemplate = string | (props) => any; +``` + +Defined in: [types/ColumnDef.ts:35](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L35) + +## Type Parameters + +### TProps + +`TProps` *extends* `object` diff --git a/docs/reference/index/type-aliases/ColumnFilterAutoRemoveTestFn.md b/docs/reference/index/type-aliases/ColumnFilterAutoRemoveTestFn.md new file mode 100644 index 0000000000..2842393bc1 --- /dev/null +++ b/docs/reference/index/type-aliases/ColumnFilterAutoRemoveTestFn.md @@ -0,0 +1,40 @@ +--- +id: ColumnFilterAutoRemoveTestFn +title: ColumnFilterAutoRemoveTestFn +--- + +# Type Alias: ColumnFilterAutoRemoveTestFn()\ + +```ts +type ColumnFilterAutoRemoveTestFn = (value, column?) => boolean; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:65](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L65) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](CellData.md) = [`CellData`](CellData.md) + +## Parameters + +### value + +`any` + +### column? + +[`Column`](Column.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`boolean` diff --git a/docs/reference/index/type-aliases/ColumnFiltersState.md b/docs/reference/index/type-aliases/ColumnFiltersState.md new file mode 100644 index 0000000000..5e48d56e9d --- /dev/null +++ b/docs/reference/index/type-aliases/ColumnFiltersState.md @@ -0,0 +1,12 @@ +--- +id: ColumnFiltersState +title: ColumnFiltersState +--- + +# Type Alias: ColumnFiltersState + +```ts +type ColumnFiltersState = ColumnFilter[]; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:22](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L22) diff --git a/docs/reference/index/type-aliases/ColumnHelper.md b/docs/reference/index/type-aliases/ColumnHelper.md new file mode 100644 index 0000000000..f083e53397 --- /dev/null +++ b/docs/reference/index/type-aliases/ColumnHelper.md @@ -0,0 +1,165 @@ +--- +id: ColumnHelper +title: ColumnHelper +--- + +# Type Alias: ColumnHelper\ + +```ts +type ColumnHelper = object; +``` + +Defined in: [helpers/columnHelper.ts:13](https://github.com/TanStack/table/blob/main/packages/table-core/src/helpers/columnHelper.ts#L13) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +## Properties + +### accessor() + +```ts +accessor: (accessor, column) => TAccessor extends AccessorFn ? AccessorFnColumnDef : AccessorKeyColumnDef; +``` + +Defined in: [helpers/columnHelper.ts:25](https://github.com/TanStack/table/blob/main/packages/table-core/src/helpers/columnHelper.ts#L25) + +Creates a data column definition with an accessor key or function to extract the cell value. + +#### Type Parameters + +##### TAccessor + +`TAccessor` *extends* + \| [`AccessorFn`](AccessorFn.md)\<`TData`\> + \| [`DeepKeys`](DeepKeys.md)\<`TData`\> + +##### TValue + +`TValue` *extends* `TAccessor` *extends* [`AccessorFn`](AccessorFn.md)\<`TData`, infer TReturn\> ? `TReturn` : `TAccessor` *extends* [`DeepKeys`](DeepKeys.md)\<`TData`\> ? [`DeepValue`](DeepValue.md)\<`TData`, `TAccessor`\> : `never` + +#### Parameters + +##### accessor + +`TAccessor` + +##### column + +`TAccessor` *extends* [`AccessorFn`](AccessorFn.md)\<`TData`\> ? [`DisplayColumnDef`](DisplayColumnDef.md)\<`TFeatures`, `TData`, `TValue`\> : [`IdentifiedColumnDef`](IdentifiedColumnDef.md)\<`TFeatures`, `TData`, `TValue`\> + +#### Returns + +`TAccessor` *extends* [`AccessorFn`](AccessorFn.md)\<`TData`\> ? [`AccessorFnColumnDef`](AccessorFnColumnDef.md)\<`TFeatures`, `TData`, `TValue`\> : [`AccessorKeyColumnDef`](AccessorKeyColumnDef.md)\<`TFeatures`, `TData`, `TValue`\> + +#### Example + +```ts +helper.accessor('firstName', { cell: (info) => info.getValue() }) +helper.accessor((row) => row.lastName, { id: 'lastName' }) +``` + +*** + +### columns() + +```ts +columns: (columns) => ColumnDef[] & [...TColumns]; +``` + +Defined in: [helpers/columnHelper.ts:48](https://github.com/TanStack/table/blob/main/packages/table-core/src/helpers/columnHelper.ts#L48) + +Wraps an array of column definitions to preserve each column's individual TValue type. +Uses variadic tuple types to infer element types before checking constraints, preventing type widening. + +#### Type Parameters + +##### TColumns + +`TColumns` *extends* `ReadonlyArray`\<[`ColumnDef`](ColumnDef.md)\<`TFeatures`, `TData`, `any`\>\> + +#### Parameters + +##### columns + +\[`...TColumns`\] + +#### Returns + +[`ColumnDef`](ColumnDef.md)\<`TFeatures`, `TData`, `any`\>[] & \[`...TColumns`\] + +#### Example + +```ts +helper.columns([helper.accessor('firstName', {}), helper.accessor('age', {})]) +``` + +*** + +### display() + +```ts +display: (column) => DisplayColumnDef; +``` + +Defined in: [helpers/columnHelper.ts:58](https://github.com/TanStack/table/blob/main/packages/table-core/src/helpers/columnHelper.ts#L58) + +Creates a display column definition for non-data columns like actions or row selection. + +#### Parameters + +##### column + +[`DisplayColumnDef`](DisplayColumnDef.md)\<`TFeatures`, `TData`\> + +#### Returns + +[`DisplayColumnDef`](DisplayColumnDef.md)\<`TFeatures`, `TData`, `unknown`\> + +#### Example + +```ts +helper.display({ id: 'actions', header: 'Actions', cell: () => }) +``` + +*** + +### group() + +```ts +group: (column) => GroupColumnDef; +``` + +Defined in: [helpers/columnHelper.ts:75](https://github.com/TanStack/table/blob/main/packages/table-core/src/helpers/columnHelper.ts#L75) + +Creates a group column definition that contains nested child columns. + +#### Parameters + +##### column + +[`GroupColumnDef`](GroupColumnDef.md)\<`TFeatures`, `TData`, `unknown`\> + +#### Returns + +[`GroupColumnDef`](GroupColumnDef.md)\<`TFeatures`, `TData`, `unknown`\> + +#### Example + +```ts +helper.group({ + id: 'name', + header: 'Name', + columns: helper.columns([ + helper.accessor('firstName', {}), + helper.accessor('lastName', { id: 'lastName' }), + ]), +}) +``` diff --git a/docs/reference/index/type-aliases/ColumnOrderState.md b/docs/reference/index/type-aliases/ColumnOrderState.md new file mode 100644 index 0000000000..462a70f464 --- /dev/null +++ b/docs/reference/index/type-aliases/ColumnOrderState.md @@ -0,0 +1,12 @@ +--- +id: ColumnOrderState +title: ColumnOrderState +--- + +# Type Alias: ColumnOrderState + +```ts +type ColumnOrderState = string[]; +``` + +Defined in: [features/column-ordering/columnOrderingFeature.types.ts:5](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-ordering/columnOrderingFeature.types.ts#L5) diff --git a/docs/reference/index/type-aliases/ColumnPinningPosition.md b/docs/reference/index/type-aliases/ColumnPinningPosition.md new file mode 100644 index 0000000000..c95fdc43e8 --- /dev/null +++ b/docs/reference/index/type-aliases/ColumnPinningPosition.md @@ -0,0 +1,12 @@ +--- +id: ColumnPinningPosition +title: ColumnPinningPosition +--- + +# Type Alias: ColumnPinningPosition + +```ts +type ColumnPinningPosition = false | "left" | "right"; +``` + +Defined in: [features/column-pinning/columnPinningFeature.types.ts:8](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.types.ts#L8) diff --git a/docs/reference/index/type-aliases/ColumnResizeDirection.md b/docs/reference/index/type-aliases/ColumnResizeDirection.md new file mode 100644 index 0000000000..89df72023b --- /dev/null +++ b/docs/reference/index/type-aliases/ColumnResizeDirection.md @@ -0,0 +1,12 @@ +--- +id: ColumnResizeDirection +title: ColumnResizeDirection +--- + +# Type Alias: ColumnResizeDirection + +```ts +type ColumnResizeDirection = "ltr" | "rtl"; +``` + +Defined in: [features/column-resizing/columnResizingFeature.types.ts:18](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts#L18) diff --git a/docs/reference/index/type-aliases/ColumnResizeMode.md b/docs/reference/index/type-aliases/ColumnResizeMode.md new file mode 100644 index 0000000000..b9367fcfd0 --- /dev/null +++ b/docs/reference/index/type-aliases/ColumnResizeMode.md @@ -0,0 +1,12 @@ +--- +id: ColumnResizeMode +title: ColumnResizeMode +--- + +# Type Alias: ColumnResizeMode + +```ts +type ColumnResizeMode = "onChange" | "onEnd"; +``` + +Defined in: [features/column-resizing/columnResizingFeature.types.ts:16](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts#L16) diff --git a/docs/reference/index/type-aliases/ColumnResizingDefaultOptions.md b/docs/reference/index/type-aliases/ColumnResizingDefaultOptions.md new file mode 100644 index 0000000000..e2087555bd --- /dev/null +++ b/docs/reference/index/type-aliases/ColumnResizingDefaultOptions.md @@ -0,0 +1,15 @@ +--- +id: ColumnResizingDefaultOptions +title: ColumnResizingDefaultOptions +--- + +# Type Alias: ColumnResizingDefaultOptions + +```ts +type ColumnResizingDefaultOptions = Pick; +``` + +Defined in: [features/column-resizing/columnResizingFeature.types.ts:43](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts#L43) diff --git a/docs/reference/index/type-aliases/ColumnSizingDefaultOptions.md b/docs/reference/index/type-aliases/ColumnSizingDefaultOptions.md new file mode 100644 index 0000000000..e6f1a3ba7c --- /dev/null +++ b/docs/reference/index/type-aliases/ColumnSizingDefaultOptions.md @@ -0,0 +1,12 @@ +--- +id: ColumnSizingDefaultOptions +title: ColumnSizingDefaultOptions +--- + +# Type Alias: ColumnSizingDefaultOptions + +```ts +type ColumnSizingDefaultOptions = Pick; +``` + +Defined in: [features/column-sizing/columnSizingFeature.types.ts:19](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.types.ts#L19) diff --git a/docs/reference/index/type-aliases/ColumnSizingState.md b/docs/reference/index/type-aliases/ColumnSizingState.md new file mode 100644 index 0000000000..b486a73a1f --- /dev/null +++ b/docs/reference/index/type-aliases/ColumnSizingState.md @@ -0,0 +1,12 @@ +--- +id: ColumnSizingState +title: ColumnSizingState +--- + +# Type Alias: ColumnSizingState + +```ts +type ColumnSizingState = Record; +``` + +Defined in: [features/column-sizing/columnSizingFeature.types.ts:8](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.types.ts#L8) diff --git a/docs/reference/index/type-aliases/ColumnVisibilityState.md b/docs/reference/index/type-aliases/ColumnVisibilityState.md new file mode 100644 index 0000000000..9ee20f8915 --- /dev/null +++ b/docs/reference/index/type-aliases/ColumnVisibilityState.md @@ -0,0 +1,12 @@ +--- +id: ColumnVisibilityState +title: ColumnVisibilityState +--- + +# Type Alias: ColumnVisibilityState + +```ts +type ColumnVisibilityState = Record; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.types.ts:6](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.types.ts#L6) diff --git a/docs/reference/index/type-aliases/Column_Internal.md b/docs/reference/index/type-aliases/Column_Internal.md new file mode 100644 index 0000000000..84c8bab815 --- /dev/null +++ b/docs/reference/index/type-aliases/Column_Internal.md @@ -0,0 +1,34 @@ +--- +id: Column_Internal +title: Column_Internal +--- + +# Type Alias: Column\_Internal\ + +```ts +type Column_Internal = Column & object; +``` + +Defined in: [types/Column.ts:80](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Column.ts#L80) + +## Type Declaration + +### columnDef + +```ts +columnDef: ColumnDefBase_All; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +### TValue + +`TValue` = `unknown` diff --git a/docs/reference/index/type-aliases/ConstructTableAPIs.md b/docs/reference/index/type-aliases/ConstructTableAPIs.md new file mode 100644 index 0000000000..1f9225eb7e --- /dev/null +++ b/docs/reference/index/type-aliases/ConstructTableAPIs.md @@ -0,0 +1,38 @@ +--- +id: ConstructTableAPIs +title: ConstructTableAPIs +--- + +# Type Alias: ConstructTableAPIs()\ + +```ts +type ConstructTableAPIs = (table) => void; +``` + +Defined in: [types/TableFeatures.ts:45](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L45) + +## Type Parameters + +### TConstructors + +`TConstructors` *extends* [`FeatureConstructors`](../interfaces/FeatureConstructors.md) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +## Parameters + +### table + +[`Table_Internal`](Table_Internal.md)\<`TFeatures`, `TData`\> & `Partial`\<`TConstructors`\[`"Table"`\]\> & `object` + +## Returns + +`void` diff --git a/docs/reference/index/type-aliases/CreateRowModels.md b/docs/reference/index/type-aliases/CreateRowModels.md new file mode 100644 index 0000000000..d2bcc1f32f --- /dev/null +++ b/docs/reference/index/type-aliases/CreateRowModels.md @@ -0,0 +1,28 @@ +--- +id: CreateRowModels +title: CreateRowModels +--- + +# Type Alias: CreateRowModels\ + +```ts +type CreateRowModels = CreateRowModel_Core & UnionToIntersection< + | "columnFacetingFeature" extends keyof TFeatures ? CreateRowModel_Faceted : never + | "columnFilteringFeature" extends keyof TFeatures ? CreateRowModel_Filtered : never + | "rowExpandingFeature" extends keyof TFeatures ? CreateRowModel_Expanded : never + | "columnGroupingFeature" extends keyof TFeatures ? CreateRowModel_Grouped : never + | "rowPaginationFeature" extends keyof TFeatures ? CreateRowModel_Paginated : never +| "rowSortingFeature" extends keyof TFeatures ? CreateRowModel_Sorted : never> & ExtractFeatureTypes<"CreateRowModels", TFeatures> & CreateRowModels_Plugins; +``` + +Defined in: [types/RowModel.ts:42](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/RowModel.ts#L42) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) diff --git a/docs/reference/index/type-aliases/CreateRowModels_All.md b/docs/reference/index/type-aliases/CreateRowModels_All.md new file mode 100644 index 0000000000..8ae438283e --- /dev/null +++ b/docs/reference/index/type-aliases/CreateRowModels_All.md @@ -0,0 +1,22 @@ +--- +id: CreateRowModels_All +title: CreateRowModels_All +--- + +# Type Alias: CreateRowModels\_All\ + +```ts +type CreateRowModels_All = CreateRowModel_Core & CreateRowModel_Expanded & CreateRowModel_Faceted & CreateRowModel_Filtered & CreateRowModel_Grouped & CreateRowModel_Paginated & CreateRowModel_Sorted; +``` + +Defined in: [types/RowModel.ts:76](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/RowModel.ts#L76) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) diff --git a/docs/reference/index/type-aliases/CustomAggregationFns.md b/docs/reference/index/type-aliases/CustomAggregationFns.md new file mode 100644 index 0000000000..e5ffaa6bb5 --- /dev/null +++ b/docs/reference/index/type-aliases/CustomAggregationFns.md @@ -0,0 +1,22 @@ +--- +id: CustomAggregationFns +title: CustomAggregationFns +--- + +# Type Alias: CustomAggregationFns\ + +```ts +type CustomAggregationFns = Record>; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:39](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L39) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) diff --git a/docs/reference/index/type-aliases/CustomFilterFns.md b/docs/reference/index/type-aliases/CustomFilterFns.md new file mode 100644 index 0000000000..e6d01a4713 --- /dev/null +++ b/docs/reference/index/type-aliases/CustomFilterFns.md @@ -0,0 +1,22 @@ +--- +id: CustomFilterFns +title: CustomFilterFns +--- + +# Type Alias: CustomFilterFns\ + +```ts +type CustomFilterFns = Record>; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:71](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L71) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) diff --git a/docs/reference/index/type-aliases/CustomSortFns.md b/docs/reference/index/type-aliases/CustomSortFns.md new file mode 100644 index 0000000000..9543336bdf --- /dev/null +++ b/docs/reference/index/type-aliases/CustomSortFns.md @@ -0,0 +1,22 @@ +--- +id: CustomSortFns +title: CustomSortFns +--- + +# Type Alias: CustomSortFns\ + +```ts +type CustomSortFns = Record>; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:41](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L41) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) diff --git a/docs/reference/index/type-aliases/DebugOptions.md b/docs/reference/index/type-aliases/DebugOptions.md new file mode 100644 index 0000000000..24f4b37697 --- /dev/null +++ b/docs/reference/index/type-aliases/DebugOptions.md @@ -0,0 +1,62 @@ +--- +id: DebugOptions +title: DebugOptions +--- + +# Type Alias: DebugOptions\ + +```ts +type DebugOptions = object & DebugKeysFor; +``` + +Defined in: [types/TableOptions.ts:41](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L41) + +## Type Declaration + +### debugAll? + +```ts +optional debugAll: boolean; +``` + +### debugCache? + +```ts +optional debugCache: boolean; +``` + +### debugCells? + +```ts +optional debugCells: boolean; +``` + +### debugColumns? + +```ts +optional debugColumns: boolean; +``` + +### debugHeaders? + +```ts +optional debugHeaders: boolean; +``` + +### debugRows? + +```ts +optional debugRows: boolean; +``` + +### debugTable? + +```ts +optional debugTable: boolean; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) diff --git a/docs/reference/index/type-aliases/DeepKeys.md b/docs/reference/index/type-aliases/DeepKeys.md new file mode 100644 index 0000000000..492a70f0ca --- /dev/null +++ b/docs/reference/index/type-aliases/DeepKeys.md @@ -0,0 +1,24 @@ +--- +id: DeepKeys +title: DeepKeys +--- + +# Type Alias: DeepKeys\ + +```ts +type DeepKeys = TDepth["length"] extends 5 ? never : unknown extends T ? string : T extends ReadonlyArray & IsTuple ? + | AllowedIndexes + | DeepKeysPrefix, TDepth> : T extends any[] ? DeepKeys : T extends Date ? never : T extends object ? keyof T & string | DeepKeysPrefix : never; +``` + +Defined in: [types/type-utils.ts:46](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/type-utils.ts#L46) + +## Type Parameters + +### T + +`T` + +### TDepth + +`TDepth` *extends* `any`[] = \[\] diff --git a/docs/reference/index/type-aliases/DeepValue.md b/docs/reference/index/type-aliases/DeepValue.md new file mode 100644 index 0000000000..a218d29cef --- /dev/null +++ b/docs/reference/index/type-aliases/DeepValue.md @@ -0,0 +1,22 @@ +--- +id: DeepValue +title: DeepValue +--- + +# Type Alias: DeepValue\ + +```ts +type DeepValue = T extends Record ? TProp extends `${infer TBranch}.${infer TDeepProp}` ? DeepValue : T[TProp & string] : never; +``` + +Defined in: [types/type-utils.ts:71](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/type-utils.ts#L71) + +## Type Parameters + +### T + +`T` + +### TProp + +`TProp` diff --git a/docs/reference/index/type-aliases/DisplayColumnDef.md b/docs/reference/index/type-aliases/DisplayColumnDef.md new file mode 100644 index 0000000000..ace9c7ea70 --- /dev/null +++ b/docs/reference/index/type-aliases/DisplayColumnDef.md @@ -0,0 +1,26 @@ +--- +id: DisplayColumnDef +title: DisplayColumnDef +--- + +# Type Alias: DisplayColumnDef\ + +```ts +type DisplayColumnDef = ColumnDefBase & ColumnIdentifiers; +``` + +Defined in: [types/ColumnDef.ts:142](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L142) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](CellData.md) = [`CellData`](CellData.md) diff --git a/docs/reference/index/type-aliases/ExpandedState.md b/docs/reference/index/type-aliases/ExpandedState.md new file mode 100644 index 0000000000..3f083c0be2 --- /dev/null +++ b/docs/reference/index/type-aliases/ExpandedState.md @@ -0,0 +1,12 @@ +--- +id: ExpandedState +title: ExpandedState +--- + +# Type Alias: ExpandedState + +```ts +type ExpandedState = true | Record; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:8](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L8) diff --git a/docs/reference/index/type-aliases/ExpandedStateList.md b/docs/reference/index/type-aliases/ExpandedStateList.md new file mode 100644 index 0000000000..f61d5b51f1 --- /dev/null +++ b/docs/reference/index/type-aliases/ExpandedStateList.md @@ -0,0 +1,12 @@ +--- +id: ExpandedStateList +title: ExpandedStateList +--- + +# Type Alias: ExpandedStateList + +```ts +type ExpandedStateList = Record; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.types.ts:7](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.types.ts#L7) diff --git a/docs/reference/index/type-aliases/ExternalAtoms.md b/docs/reference/index/type-aliases/ExternalAtoms.md new file mode 100644 index 0000000000..65d08a4bec --- /dev/null +++ b/docs/reference/index/type-aliases/ExternalAtoms.md @@ -0,0 +1,22 @@ +--- +id: ExternalAtoms +title: ExternalAtoms +--- + +# Type Alias: ExternalAtoms\ + +```ts +type ExternalAtoms = Partial<{ [K in keyof TableState]: Atom[K]> }>; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:40](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L40) + +A map of optional external atoms, one per `TableState` slice. Consumers can +provide their own writable atom for any state slice to take over ownership +of that slice. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) diff --git a/docs/reference/index/type-aliases/ExternalAtoms_All.md b/docs/reference/index/type-aliases/ExternalAtoms_All.md new file mode 100644 index 0000000000..154e3515c5 --- /dev/null +++ b/docs/reference/index/type-aliases/ExternalAtoms_All.md @@ -0,0 +1,12 @@ +--- +id: ExternalAtoms_All +title: ExternalAtoms_All +--- + +# Type Alias: ExternalAtoms\_All + +```ts +type ExternalAtoms_All = Partial<{ [K in keyof TableState_All]: Atom }>; +``` + +Defined in: [core/table/coreTablesFeature.types.ts:60](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.types.ts#L60) diff --git a/docs/reference/index/type-aliases/ExtractFeatureTypes.md b/docs/reference/index/type-aliases/ExtractFeatureTypes.md new file mode 100644 index 0000000000..47dc2e13ae --- /dev/null +++ b/docs/reference/index/type-aliases/ExtractFeatureTypes.md @@ -0,0 +1,22 @@ +--- +id: ExtractFeatureTypes +title: ExtractFeatureTypes +--- + +# Type Alias: ExtractFeatureTypes\ + +```ts +type ExtractFeatureTypes = UnionToIntersection<{ [K in keyof TFeatures]: K extends "coreReativityFeature" ? never : TFeatures[K] extends TableFeature ? TKey extends keyof FeatureConstructorOptions ? FeatureConstructorOptions[TKey] : never : any }[keyof TFeatures]>; +``` + +Defined in: [types/TableFeatures.ts:10](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L10) + +## Type Parameters + +### TKey + +`TKey` *extends* keyof [`FeatureConstructors`](../interfaces/FeatureConstructors.md) + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) diff --git a/docs/reference/index/type-aliases/FilterFnOption.md b/docs/reference/index/type-aliases/FilterFnOption.md new file mode 100644 index 0000000000..3cb48c19c4 --- /dev/null +++ b/docs/reference/index/type-aliases/FilterFnOption.md @@ -0,0 +1,26 @@ +--- +id: FilterFnOption +title: FilterFnOption +--- + +# Type Alias: FilterFnOption\ + +```ts +type FilterFnOption = + | "auto" + | BuiltInFilterFn + | keyof FilterFns +| FilterFn; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:76](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L76) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) diff --git a/docs/reference/index/type-aliases/GetDefaultColumnDef.md b/docs/reference/index/type-aliases/GetDefaultColumnDef.md new file mode 100644 index 0000000000..270ff5abd0 --- /dev/null +++ b/docs/reference/index/type-aliases/GetDefaultColumnDef.md @@ -0,0 +1,36 @@ +--- +id: GetDefaultColumnDef +title: GetDefaultColumnDef +--- + +# Type Alias: GetDefaultColumnDef()\ + +```ts +type GetDefaultColumnDef = () => ColumnDefBase_All & Partial; +``` + +Defined in: [types/TableFeatures.ts:55](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L55) + +## Type Parameters + +### TConstructors + +`TConstructors` *extends* [`FeatureConstructors`](../interfaces/FeatureConstructors.md) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](CellData.md) = [`CellData`](CellData.md) + +## Returns + +[`ColumnDefBase_All`](ColumnDefBase_All.md)\<`TFeatures`, `TData`, `TValue`\> & `Partial`\<`TConstructors`\[`"ColumnDef"`\]\> diff --git a/docs/reference/index/type-aliases/GetDefaultStateSelector.md b/docs/reference/index/type-aliases/GetDefaultStateSelector.md new file mode 100644 index 0000000000..321bba0ed7 --- /dev/null +++ b/docs/reference/index/type-aliases/GetDefaultStateSelector.md @@ -0,0 +1,28 @@ +--- +id: GetDefaultStateSelector +title: GetDefaultStateSelector +--- + +# Type Alias: GetDefaultStateSelector()\ + +```ts +type GetDefaultStateSelector = (state) => Partial & Partial; +``` + +Defined in: [types/TableFeatures.ts:72](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L72) + +## Type Parameters + +### TConstructors + +`TConstructors` *extends* [`FeatureConstructors`](../interfaces/FeatureConstructors.md) + +## Parameters + +### state + +[`TableState_All`](TableState_All.md) + +## Returns + +`Partial`\<[`TableState_All`](TableState_All.md)\> & `Partial`\<`TConstructors`\[`"TableState"`\]\> diff --git a/docs/reference/index/type-aliases/GetDefaultTableOptions.md b/docs/reference/index/type-aliases/GetDefaultTableOptions.md new file mode 100644 index 0000000000..14e838d76a --- /dev/null +++ b/docs/reference/index/type-aliases/GetDefaultTableOptions.md @@ -0,0 +1,38 @@ +--- +id: GetDefaultTableOptions +title: GetDefaultTableOptions +--- + +# Type Alias: GetDefaultTableOptions()\ + +```ts +type GetDefaultTableOptions = (table) => Partial> & Partial; +``` + +Defined in: [types/TableFeatures.ts:62](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L62) + +## Type Parameters + +### TConstructors + +`TConstructors` *extends* [`FeatureConstructors`](../interfaces/FeatureConstructors.md) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +## Parameters + +### table + +[`Table_Internal`](Table_Internal.md)\<`TFeatures`, `TData`\> & `Partial`\<`TConstructors`\[`"Table"`\]\> + +## Returns + +`Partial`\<[`TableOptions_All`](TableOptions_All.md)\<`TFeatures`, `TData`\>\> & `Partial`\<`TConstructors`\[`"TableOptions"`\]\> diff --git a/docs/reference/index/type-aliases/GetInitialState.md b/docs/reference/index/type-aliases/GetInitialState.md new file mode 100644 index 0000000000..f8d29d1c9f --- /dev/null +++ b/docs/reference/index/type-aliases/GetInitialState.md @@ -0,0 +1,28 @@ +--- +id: GetInitialState +title: GetInitialState +--- + +# Type Alias: GetInitialState()\ + +```ts +type GetInitialState = (initialState) => TableState_All & Partial; +``` + +Defined in: [types/TableFeatures.ts:68](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L68) + +## Type Parameters + +### TConstructors + +`TConstructors` *extends* [`FeatureConstructors`](../interfaces/FeatureConstructors.md) + +## Parameters + +### initialState + +`Partial`\<[`TableState_All`](TableState_All.md)\> & `Partial`\<`TConstructors`\[`"TableState"`\]\> + +## Returns + +[`TableState_All`](TableState_All.md) & `Partial`\<`TConstructors`\[`"TableState"`\]\> diff --git a/docs/reference/index/type-aliases/Getter.md b/docs/reference/index/type-aliases/Getter.md new file mode 100644 index 0000000000..3f894da629 --- /dev/null +++ b/docs/reference/index/type-aliases/Getter.md @@ -0,0 +1,28 @@ +--- +id: Getter +title: Getter +--- + +# Type Alias: Getter()\ + +```ts +type Getter = () => NoInfer; +``` + +Defined in: [types/type-utils.ts:80](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/type-utils.ts#L80) + +## Type Parameters + +### TValue + +`TValue` + +## Type Parameters + +### TTValue + +`TTValue` = `TValue` + +## Returns + +[`NoInfer`](NoInfer.md)\<`TTValue`\> diff --git a/docs/reference/index/type-aliases/GroupColumnDef.md b/docs/reference/index/type-aliases/GroupColumnDef.md new file mode 100644 index 0000000000..2a6a72d73f --- /dev/null +++ b/docs/reference/index/type-aliases/GroupColumnDef.md @@ -0,0 +1,26 @@ +--- +id: GroupColumnDef +title: GroupColumnDef +--- + +# Type Alias: GroupColumnDef\ + +```ts +type GroupColumnDef = GroupColumnDefBase & ColumnIdentifiers; +``` + +Defined in: [types/ColumnDef.ts:156](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L156) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](CellData.md) = [`CellData`](CellData.md) diff --git a/docs/reference/index/type-aliases/GroupingColumnMode.md b/docs/reference/index/type-aliases/GroupingColumnMode.md new file mode 100644 index 0000000000..3855a3ba04 --- /dev/null +++ b/docs/reference/index/type-aliases/GroupingColumnMode.md @@ -0,0 +1,12 @@ +--- +id: GroupingColumnMode +title: GroupingColumnMode +--- + +# Type Alias: GroupingColumnMode + +```ts +type GroupingColumnMode = false | "reorder" | "remove"; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:173](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L173) diff --git a/docs/reference/index/type-aliases/GroupingState.md b/docs/reference/index/type-aliases/GroupingState.md new file mode 100644 index 0000000000..40690ac7cf --- /dev/null +++ b/docs/reference/index/type-aliases/GroupingState.md @@ -0,0 +1,12 @@ +--- +id: GroupingState +title: GroupingState +--- + +# Type Alias: GroupingState + +```ts +type GroupingState = string[]; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.types.ts:15](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.types.ts#L15) diff --git a/docs/reference/index/type-aliases/Header.md b/docs/reference/index/type-aliases/Header.md new file mode 100644 index 0000000000..0a7f21d453 --- /dev/null +++ b/docs/reference/index/type-aliases/Header.md @@ -0,0 +1,28 @@ +--- +id: Header +title: Header +--- + +# Type Alias: Header\ + +```ts +type Header = Header_Core & UnionToIntersection< + | "columnSizingFeature" extends keyof TFeatures ? Header_ColumnSizing : never +| "columnResizingFeature" extends keyof TFeatures ? Header_ColumnResizing : never> & ExtractFeatureTypes<"Header", TFeatures> & Header_Plugins; +``` + +Defined in: [types/Header.ts:23](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Header.ts#L23) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](CellData.md) = [`CellData`](CellData.md) diff --git a/docs/reference/index/type-aliases/HeaderGroup.md b/docs/reference/index/type-aliases/HeaderGroup.md new file mode 100644 index 0000000000..10918eec2b --- /dev/null +++ b/docs/reference/index/type-aliases/HeaderGroup.md @@ -0,0 +1,22 @@ +--- +id: HeaderGroup +title: HeaderGroup +--- + +# Type Alias: HeaderGroup\ + +```ts +type HeaderGroup = HeaderGroup_Core & ExtractFeatureTypes<"HeaderGroup", TFeatures> & HeaderGroup_Plugins; +``` + +Defined in: [types/HeaderGroup.ts:19](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/HeaderGroup.ts#L19) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) diff --git a/docs/reference/index/type-aliases/IdentifiedColumnDef.md b/docs/reference/index/type-aliases/IdentifiedColumnDef.md new file mode 100644 index 0000000000..2dcfeafae5 --- /dev/null +++ b/docs/reference/index/type-aliases/IdentifiedColumnDef.md @@ -0,0 +1,40 @@ +--- +id: IdentifiedColumnDef +title: IdentifiedColumnDef +--- + +# Type Alias: IdentifiedColumnDef\ + +```ts +type IdentifiedColumnDef = ColumnDefBase & object; +``` + +Defined in: [types/ColumnDef.ts:133](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L133) + +## Type Declaration + +### header? + +```ts +optional header: StringOrTemplateHeader; +``` + +### id? + +```ts +optional id: string; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](CellData.md) = [`CellData`](CellData.md) diff --git a/docs/reference/index/type-aliases/InitRowInstanceData.md b/docs/reference/index/type-aliases/InitRowInstanceData.md new file mode 100644 index 0000000000..a34eb37343 --- /dev/null +++ b/docs/reference/index/type-aliases/InitRowInstanceData.md @@ -0,0 +1,38 @@ +--- +id: InitRowInstanceData +title: InitRowInstanceData +--- + +# Type Alias: InitRowInstanceData()\ + +```ts +type InitRowInstanceData = (row) => void; +``` + +Defined in: [types/TableFeatures.ts:109](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableFeatures.ts#L109) + +## Type Parameters + +### TConstructors + +`TConstructors` *extends* [`FeatureConstructors`](../interfaces/FeatureConstructors.md) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +## Parameters + +### row + +[`Row`](Row.md)\<`TFeatures`, `TData`\> & `Partial`\<`TConstructors`\[`"Row"`\]\> + +## Returns + +`void` diff --git a/docs/reference/index/type-aliases/MemoFnMeta.md b/docs/reference/index/type-aliases/MemoFnMeta.md new file mode 100644 index 0000000000..14ed5b713f --- /dev/null +++ b/docs/reference/index/type-aliases/MemoFnMeta.md @@ -0,0 +1,22 @@ +--- +id: MemoFnMeta +title: MemoFnMeta +--- + +# Type Alias: MemoFnMeta + +```ts +type MemoFnMeta = object; +``` + +Defined in: [utils.ts:116](https://github.com/TanStack/table/blob/main/packages/table-core/src/utils.ts#L116) + +## Properties + +### originalArgsLength? + +```ts +optional originalArgsLength: number; +``` + +Defined in: [utils.ts:116](https://github.com/TanStack/table/blob/main/packages/table-core/src/utils.ts#L116) diff --git a/docs/reference/index/type-aliases/NoInfer.md b/docs/reference/index/type-aliases/NoInfer.md new file mode 100644 index 0000000000..d114f1f8cc --- /dev/null +++ b/docs/reference/index/type-aliases/NoInfer.md @@ -0,0 +1,18 @@ +--- +id: NoInfer +title: NoInfer +--- + +# Type Alias: NoInfer\ + +```ts +type NoInfer = [T][T extends any ? 0 : never]; +``` + +Defined in: [types/type-utils.ts:78](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/type-utils.ts#L78) + +## Type Parameters + +### T + +`T` diff --git a/docs/reference/index/type-aliases/OnChangeFn.md b/docs/reference/index/type-aliases/OnChangeFn.md new file mode 100644 index 0000000000..6b84ff8b45 --- /dev/null +++ b/docs/reference/index/type-aliases/OnChangeFn.md @@ -0,0 +1,28 @@ +--- +id: OnChangeFn +title: OnChangeFn +--- + +# Type Alias: OnChangeFn()\ + +```ts +type OnChangeFn = (updaterOrValue) => void; +``` + +Defined in: [types/type-utils.ts:3](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/type-utils.ts#L3) + +## Type Parameters + +### T + +`T` + +## Parameters + +### updaterOrValue + +[`Updater`](Updater.md)\<`T`\> + +## Returns + +`void` diff --git a/docs/reference/index/type-aliases/PartialKeys.md b/docs/reference/index/type-aliases/PartialKeys.md new file mode 100644 index 0000000000..560bb4cfbf --- /dev/null +++ b/docs/reference/index/type-aliases/PartialKeys.md @@ -0,0 +1,22 @@ +--- +id: PartialKeys +title: PartialKeys +--- + +# Type Alias: PartialKeys\ + +```ts +type PartialKeys = Omit & Partial>; +``` + +Defined in: [types/type-utils.ts:9](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/type-utils.ts#L9) + +## Type Parameters + +### T + +`T` + +### K + +`K` *extends* keyof `T` diff --git a/docs/reference/index/type-aliases/Prettify.md b/docs/reference/index/type-aliases/Prettify.md new file mode 100644 index 0000000000..308b5177aa --- /dev/null +++ b/docs/reference/index/type-aliases/Prettify.md @@ -0,0 +1,18 @@ +--- +id: Prettify +title: Prettify +--- + +# Type Alias: Prettify\ + +```ts +type Prettify = { [K in keyof T]: T[K] } & unknown; +``` + +Defined in: [types/type-utils.ts:82](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/type-utils.ts#L82) + +## Type Parameters + +### T + +`T` diff --git a/docs/reference/index/type-aliases/PrototypeAPIObject.md b/docs/reference/index/type-aliases/PrototypeAPIObject.md new file mode 100644 index 0000000000..8b0f5b650c --- /dev/null +++ b/docs/reference/index/type-aliases/PrototypeAPIObject.md @@ -0,0 +1,22 @@ +--- +id: PrototypeAPIObject +title: PrototypeAPIObject +--- + +# Type Alias: PrototypeAPIObject\ + +```ts +type PrototypeAPIObject = Record>; +``` + +Defined in: [utils.ts:391](https://github.com/TanStack/table/blob/main/packages/table-core/src/utils.ts#L391) + +## Type Parameters + +### TDeps + +`TDeps` *extends* `ReadonlyArray`\<`any`\> + +### TDepArgs + +`TDepArgs` diff --git a/docs/reference/index/type-aliases/RequiredKeys.md b/docs/reference/index/type-aliases/RequiredKeys.md new file mode 100644 index 0000000000..bbf82b7740 --- /dev/null +++ b/docs/reference/index/type-aliases/RequiredKeys.md @@ -0,0 +1,22 @@ +--- +id: RequiredKeys +title: RequiredKeys +--- + +# Type Alias: RequiredKeys\ + +```ts +type RequiredKeys = Omit & Required>; +``` + +Defined in: [types/type-utils.ts:11](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/type-utils.ts#L11) + +## Type Parameters + +### T + +`T` + +### K + +`K` *extends* keyof `T` diff --git a/docs/reference/index/type-aliases/Row.md b/docs/reference/index/type-aliases/Row.md new file mode 100644 index 0000000000..3952ae74ba --- /dev/null +++ b/docs/reference/index/type-aliases/Row.md @@ -0,0 +1,29 @@ +--- +id: Row +title: Row +--- + +# Type Alias: Row\ + +```ts +type Row = Row_Core & UnionToIntersection< + | "columnFilteringFeature" extends keyof TFeatures ? Row_ColumnFiltering : never + | "columnGroupingFeature" extends keyof TFeatures ? Row_ColumnGrouping : never + | "columnPinningFeature" extends keyof TFeatures ? Row_ColumnPinning : never + | "columnVisibilityFeature" extends keyof TFeatures ? Row_ColumnVisibility : never + | "rowExpandingFeature" extends keyof TFeatures ? Row_RowExpanding : never + | "rowPinningFeature" extends keyof TFeatures ? Row_RowPinning : never +| "rowSelectionFeature" extends keyof TFeatures ? Row_RowSelection : never> & ExtractFeatureTypes<"Row", TFeatures> & Row_Plugins; +``` + +Defined in: [types/Row.ts:26](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Row.ts#L26) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) diff --git a/docs/reference/index/type-aliases/RowData.md b/docs/reference/index/type-aliases/RowData.md new file mode 100644 index 0000000000..752ab32417 --- /dev/null +++ b/docs/reference/index/type-aliases/RowData.md @@ -0,0 +1,12 @@ +--- +id: RowData +title: RowData +--- + +# Type Alias: RowData + +```ts +type RowData = Record | any[]; +``` + +Defined in: [types/type-utils.ts:5](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/type-utils.ts#L5) diff --git a/docs/reference/index/type-aliases/RowModelFns.md b/docs/reference/index/type-aliases/RowModelFns.md new file mode 100644 index 0000000000..35fb67dbbc --- /dev/null +++ b/docs/reference/index/type-aliases/RowModelFns.md @@ -0,0 +1,25 @@ +--- +id: RowModelFns +title: RowModelFns +--- + +# Type Alias: RowModelFns\ + +```ts +type RowModelFns = Partial : never + | "columnGroupingFeature" extends keyof TFeatures ? RowModelFns_ColumnGrouping : never +| "rowSortingFeature" extends keyof TFeatures ? RowModelFns_RowSorting : never> & ExtractFeatureTypes<"RowModelFns", TFeatures> & RowModelFns_Plugins>; +``` + +Defined in: [types/RowModelFns.ts:18](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/RowModelFns.ts#L18) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) diff --git a/docs/reference/index/type-aliases/RowModelFns_All.md b/docs/reference/index/type-aliases/RowModelFns_All.md new file mode 100644 index 0000000000..fcef1570c3 --- /dev/null +++ b/docs/reference/index/type-aliases/RowModelFns_All.md @@ -0,0 +1,22 @@ +--- +id: RowModelFns_All +title: RowModelFns_All +--- + +# Type Alias: RowModelFns\_All\ + +```ts +type RowModelFns_All = Partial & RowModelFns_ColumnGrouping & RowModelFns_RowSorting>; +``` + +Defined in: [types/RowModelFns.ts:44](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/RowModelFns.ts#L44) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) diff --git a/docs/reference/index/type-aliases/RowPinningPosition.md b/docs/reference/index/type-aliases/RowPinningPosition.md new file mode 100644 index 0000000000..4aeb8352ab --- /dev/null +++ b/docs/reference/index/type-aliases/RowPinningPosition.md @@ -0,0 +1,12 @@ +--- +id: RowPinningPosition +title: RowPinningPosition +--- + +# Type Alias: RowPinningPosition + +```ts +type RowPinningPosition = false | "top" | "bottom"; +``` + +Defined in: [features/row-pinning/rowPinningFeature.types.ts:5](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.types.ts#L5) diff --git a/docs/reference/index/type-aliases/RowSelectionState.md b/docs/reference/index/type-aliases/RowSelectionState.md new file mode 100644 index 0000000000..f5f454a788 --- /dev/null +++ b/docs/reference/index/type-aliases/RowSelectionState.md @@ -0,0 +1,12 @@ +--- +id: RowSelectionState +title: RowSelectionState +--- + +# Type Alias: RowSelectionState + +```ts +type RowSelectionState = Record; +``` + +Defined in: [features/row-selection/rowSelectionFeature.types.ts:6](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.types.ts#L6) diff --git a/docs/reference/index/type-aliases/SortDirection.md b/docs/reference/index/type-aliases/SortDirection.md new file mode 100644 index 0000000000..6f7c1078ab --- /dev/null +++ b/docs/reference/index/type-aliases/SortDirection.md @@ -0,0 +1,12 @@ +--- +id: SortDirection +title: SortDirection +--- + +# Type Alias: SortDirection + +```ts +type SortDirection = "asc" | "desc"; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:8](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L8) diff --git a/docs/reference/index/type-aliases/SortFnOption.md b/docs/reference/index/type-aliases/SortFnOption.md new file mode 100644 index 0000000000..bdca371be4 --- /dev/null +++ b/docs/reference/index/type-aliases/SortFnOption.md @@ -0,0 +1,26 @@ +--- +id: SortFnOption +title: SortFnOption +--- + +# Type Alias: SortFnOption\ + +```ts +type SortFnOption = + | "auto" + | keyof SortFns + | BuiltInSortFn +| SortFn; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:46](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L46) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) diff --git a/docs/reference/index/type-aliases/SortingState.md b/docs/reference/index/type-aliases/SortingState.md new file mode 100644 index 0000000000..954185c4d8 --- /dev/null +++ b/docs/reference/index/type-aliases/SortingState.md @@ -0,0 +1,12 @@ +--- +id: SortingState +title: SortingState +--- + +# Type Alias: SortingState + +```ts +type SortingState = ColumnSort[]; +``` + +Defined in: [features/row-sorting/rowSortingFeature.types.ts:15](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts#L15) diff --git a/docs/reference/index/type-aliases/StringOrTemplateHeader.md b/docs/reference/index/type-aliases/StringOrTemplateHeader.md new file mode 100644 index 0000000000..b63735544b --- /dev/null +++ b/docs/reference/index/type-aliases/StringOrTemplateHeader.md @@ -0,0 +1,28 @@ +--- +id: StringOrTemplateHeader +title: StringOrTemplateHeader +--- + +# Type Alias: StringOrTemplateHeader\ + +```ts +type StringOrTemplateHeader = + | string +| ColumnDefTemplate>; +``` + +Defined in: [types/ColumnDef.ts:39](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/ColumnDef.ts#L39) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](CellData.md) = [`CellData`](CellData.md) diff --git a/docs/reference/index/type-aliases/Table.md b/docs/reference/index/type-aliases/Table.md new file mode 100644 index 0000000000..26cf483e5e --- /dev/null +++ b/docs/reference/index/type-aliases/Table.md @@ -0,0 +1,38 @@ +--- +id: Table +title: Table +--- + +# Type Alias: Table\ + +```ts +type Table = Table_Core & UnionToIntersection< + | "columnFilteringFeature" extends keyof TFeatures ? Table_ColumnFiltering : never + | "columnGroupingFeature" extends keyof TFeatures ? Table_ColumnGrouping : never + | "columnOrderingFeature" extends keyof TFeatures ? Table_ColumnOrdering : never + | "columnPinningFeature" extends keyof TFeatures ? Table_ColumnPinning : never + | "columnResizingFeature" extends keyof TFeatures ? Table_ColumnResizing : never + | "columnSizingFeature" extends keyof TFeatures ? Table_ColumnSizing : never + | "columnVisibilityFeature" extends keyof TFeatures ? Table_ColumnVisibility : never + | "columnFacetingFeature" extends keyof TFeatures ? Table_ColumnFaceting : never + | "globalFilteringFeature" extends keyof TFeatures ? Table_GlobalFiltering : never + | "rowExpandingFeature" extends keyof TFeatures ? Table_RowExpanding : never + | "rowPaginationFeature" extends keyof TFeatures ? Table_RowPagination : never + | "rowPinningFeature" extends keyof TFeatures ? Table_RowPinning : never + | "rowSelectionFeature" extends keyof TFeatures ? Table_RowSelection : never +| "rowSortingFeature" extends keyof TFeatures ? Table_RowSorting : never> & ExtractFeatureTypes<"Table", TFeatures> & Table_Plugins; +``` + +Defined in: [types/Table.ts:58](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L58) + +The table object that includes both the core table functionality and the features that are enabled via the `_features` table option. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) diff --git a/docs/reference/index/type-aliases/TableOptions.md b/docs/reference/index/type-aliases/TableOptions.md new file mode 100644 index 0000000000..fc7f03a758 --- /dev/null +++ b/docs/reference/index/type-aliases/TableOptions.md @@ -0,0 +1,35 @@ +--- +id: TableOptions +title: TableOptions +--- + +# Type Alias: TableOptions\ + +```ts +type TableOptions = TableOptions_Core & UnionToIntersection< + | "columnFilteringFeature" extends keyof TFeatures ? TableOptions_ColumnFiltering : never + | "columnGroupingFeature" extends keyof TFeatures ? TableOptions_ColumnGrouping : never + | "columnOrderingFeature" extends keyof TFeatures ? TableOptions_ColumnOrdering : never + | "columnPinningFeature" extends keyof TFeatures ? TableOptions_ColumnPinning : never + | "columnResizingFeature" extends keyof TFeatures ? TableOptions_ColumnResizing : never + | "columnSizingFeature" extends keyof TFeatures ? TableOptions_ColumnSizing : never + | "columnVisibilityFeature" extends keyof TFeatures ? TableOptions_ColumnVisibility : never + | "globalFilteringFeature" extends keyof TFeatures ? TableOptions_GlobalFiltering : never + | "rowExpandingFeature" extends keyof TFeatures ? TableOptions_RowExpanding : never + | "rowPaginationFeature" extends keyof TFeatures ? TableOptions_RowPagination : never + | "rowPinningFeature" extends keyof TFeatures ? TableOptions_RowPinning : never + | "rowSelectionFeature" extends keyof TFeatures ? TableOptions_RowSelection : never +| "rowSortingFeature" extends keyof TFeatures ? TableOptions_RowSorting : never> & ExtractFeatureTypes<"TableOptions", TFeatures> & TableOptions_Plugins & DebugOptions; +``` + +Defined in: [types/TableOptions.ts:51](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L51) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) diff --git a/docs/reference/index/type-aliases/TableOptions_All.md b/docs/reference/index/type-aliases/TableOptions_All.md new file mode 100644 index 0000000000..e05e46d917 --- /dev/null +++ b/docs/reference/index/type-aliases/TableOptions_All.md @@ -0,0 +1,22 @@ +--- +id: TableOptions_All +title: TableOptions_All +--- + +# Type Alias: TableOptions\_All\ + +```ts +type TableOptions_All = TableOptions_Core & Partial & TableOptions_ColumnGrouping & TableOptions_ColumnOrdering & TableOptions_ColumnPinning & TableOptions_ColumnResizing & TableOptions_ColumnSizing & TableOptions_ColumnVisibility & TableOptions_GlobalFiltering & TableOptions_RowExpanding & TableOptions_RowPagination & TableOptions_RowPinning & TableOptions_RowSelection & TableOptions_RowSorting>; +``` + +Defined in: [types/TableOptions.ts:107](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableOptions.ts#L107) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) diff --git a/docs/reference/index/type-aliases/TableState.md b/docs/reference/index/type-aliases/TableState.md new file mode 100644 index 0000000000..c8b8d8fdc2 --- /dev/null +++ b/docs/reference/index/type-aliases/TableState.md @@ -0,0 +1,31 @@ +--- +id: TableState +title: TableState +--- + +# Type Alias: TableState\ + +```ts +type TableState = UnionToIntersection< + | "columnFilteringFeature" extends keyof TFeatures ? TableState_ColumnFiltering : never + | "columnGroupingFeature" extends keyof TFeatures ? TableState_ColumnGrouping : never + | "columnOrderingFeature" extends keyof TFeatures ? TableState_ColumnOrdering : never + | "columnPinningFeature" extends keyof TFeatures ? TableState_ColumnPinning : never + | "columnResizingFeature" extends keyof TFeatures ? TableState_ColumnResizing : never + | "columnSizingFeature" extends keyof TFeatures ? TableState_ColumnSizing : never + | "columnVisibilityFeature" extends keyof TFeatures ? TableState_ColumnVisibility : never + | "globalFilteringFeature" extends keyof TFeatures ? TableState_GlobalFiltering : never + | "rowExpandingFeature" extends keyof TFeatures ? TableState_RowExpanding : never + | "rowPaginationFeature" extends keyof TFeatures ? TableState_RowPagination : never + | "rowPinningFeature" extends keyof TFeatures ? TableState_RowPinning : never + | "rowSelectionFeature" extends keyof TFeatures ? TableState_RowSelection : never +| "rowSortingFeature" extends keyof TFeatures ? TableState_RowSorting : never> & ExtractFeatureTypes<"TableState", TFeatures> & TableState_Plugins; +``` + +Defined in: [types/TableState.ts:23](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L23) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) diff --git a/docs/reference/index/type-aliases/TableState_All.md b/docs/reference/index/type-aliases/TableState_All.md new file mode 100644 index 0000000000..4062716898 --- /dev/null +++ b/docs/reference/index/type-aliases/TableState_All.md @@ -0,0 +1,12 @@ +--- +id: TableState_All +title: TableState_All +--- + +# Type Alias: TableState\_All + +```ts +type TableState_All = Partial; +``` + +Defined in: [types/TableState.ts:74](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/TableState.ts#L74) diff --git a/docs/reference/index/type-aliases/Table_Core.md b/docs/reference/index/type-aliases/Table_Core.md new file mode 100644 index 0000000000..d427a93c7d --- /dev/null +++ b/docs/reference/index/type-aliases/Table_Core.md @@ -0,0 +1,25 @@ +--- +id: Table_Core +title: Table_Core +--- + +# Type Alias: Table\_Core\ + +```ts +type Table_Core = Table_Table & Table_Columns & Table_Rows & Table_RowModels & Table_Headers; +``` + +Defined in: [types/Table.ts:46](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L46) + +The core table object that only includes the core table functionality such as column, header, row, and table APIS. +No features are included. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) diff --git a/docs/reference/index/type-aliases/Table_Internal.md b/docs/reference/index/type-aliases/Table_Internal.md new file mode 100644 index 0000000000..61d11492f0 --- /dev/null +++ b/docs/reference/index/type-aliases/Table_Internal.md @@ -0,0 +1,92 @@ +--- +id: Table_Internal +title: Table_Internal +--- + +# Type Alias: Table\_Internal\ + +```ts +type Table_Internal = Table & object; +``` + +Defined in: [types/Table.ts:116](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/Table.ts#L116) + +## Type Declaration + +### \_rowModelFns + +```ts +_rowModelFns: RowModelFns_All; +``` + +### \_rowModels + +```ts +_rowModels: CachedRowModel_All; +``` + +### atoms + +```ts +atoms: Atoms_All; +``` + +### baseAtoms + +```ts +baseAtoms: BaseAtoms_All; +``` + +### initialState + +```ts +initialState: TableState_All; +``` + +### options + +```ts +options: TableOptions_All & object; +``` + +#### Type Declaration + +##### \_rowModels? + +```ts +optional _rowModels: CreateRowModels_All; +``` + +##### atoms? + +```ts +optional atoms: ExternalAtoms_All; +``` + +##### initialState? + +```ts +optional initialState: TableState_All; +``` + +##### state? + +```ts +optional state: TableState_All; +``` + +### store + +```ts +store: ReadonlyStore; +``` + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) = `any` diff --git a/docs/reference/index/type-aliases/Table_RowModels.md b/docs/reference/index/type-aliases/Table_RowModels.md new file mode 100644 index 0000000000..8fade8a58d --- /dev/null +++ b/docs/reference/index/type-aliases/Table_RowModels.md @@ -0,0 +1,22 @@ +--- +id: Table_RowModels +title: Table_RowModels +--- + +# Type Alias: Table\_RowModels\ + +```ts +type Table_RowModels = Table_RowModels_Core & Table_RowModels_Faceted & Table_RowModels_Filtered & Table_RowModels_Grouped & Table_RowModels_Expanded & Table_RowModels_Paginated & Table_RowModels_Sorted; +``` + +Defined in: [core/row-models/coreRowModelsFeature.types.ts:59](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.types.ts#L59) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) diff --git a/docs/reference/index/type-aliases/TransformFilterValueFn.md b/docs/reference/index/type-aliases/TransformFilterValueFn.md new file mode 100644 index 0000000000..c51818af50 --- /dev/null +++ b/docs/reference/index/type-aliases/TransformFilterValueFn.md @@ -0,0 +1,40 @@ +--- +id: TransformFilterValueFn +title: TransformFilterValueFn +--- + +# Type Alias: TransformFilterValueFn()\ + +```ts +type TransformFilterValueFn = (value, column?) => TValue; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.types.ts:59](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.types.ts#L59) + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](RowData.md) + +### TValue + +`TValue` *extends* [`CellData`](CellData.md) = [`CellData`](CellData.md) + +## Parameters + +### value + +`any` + +### column? + +[`Column`](Column.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`TValue` diff --git a/docs/reference/index/type-aliases/UnionToIntersection.md b/docs/reference/index/type-aliases/UnionToIntersection.md new file mode 100644 index 0000000000..f320f56a9f --- /dev/null +++ b/docs/reference/index/type-aliases/UnionToIntersection.md @@ -0,0 +1,18 @@ +--- +id: UnionToIntersection +title: UnionToIntersection +--- + +# Type Alias: UnionToIntersection\ + +```ts +type UnionToIntersection = T extends any ? (x) => any : never extends (x) => any ? R : never; +``` + +Defined in: [types/type-utils.ts:14](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/type-utils.ts#L14) + +## Type Parameters + +### T + +`T` diff --git a/docs/reference/index/type-aliases/Updater.md b/docs/reference/index/type-aliases/Updater.md new file mode 100644 index 0000000000..f0be9f6aeb --- /dev/null +++ b/docs/reference/index/type-aliases/Updater.md @@ -0,0 +1,18 @@ +--- +id: Updater +title: Updater +--- + +# Type Alias: Updater\ + +```ts +type Updater = T | (old) => T; +``` + +Defined in: [types/type-utils.ts:1](https://github.com/TanStack/table/blob/main/packages/table-core/src/types/type-utils.ts#L1) + +## Type Parameters + +### T + +`T` diff --git a/docs/reference/index/type-aliases/VisibilityDefaultOptions.md b/docs/reference/index/type-aliases/VisibilityDefaultOptions.md new file mode 100644 index 0000000000..046d5c61a2 --- /dev/null +++ b/docs/reference/index/type-aliases/VisibilityDefaultOptions.md @@ -0,0 +1,12 @@ +--- +id: VisibilityDefaultOptions +title: VisibilityDefaultOptions +--- + +# Type Alias: VisibilityDefaultOptions + +```ts +type VisibilityDefaultOptions = Pick; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.types.ts:25](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.types.ts#L25) diff --git a/docs/reference/index/variables/$internalMemoFnMeta.md b/docs/reference/index/variables/$internalMemoFnMeta.md new file mode 100644 index 0000000000..f4de9a3285 --- /dev/null +++ b/docs/reference/index/variables/$internalMemoFnMeta.md @@ -0,0 +1,17 @@ +--- +id: $internalMemoFnMeta +title: $internalMemoFnMeta +--- + +# Variable: $internalMemoFnMeta + +```ts +const $internalMemoFnMeta: typeof $internalMemoFnMeta; +``` + +Defined in: [utils.ts:115](https://github.com/TanStack/table/blob/main/packages/table-core/src/utils.ts#L115) + +Symbol used to attach internal memo metadata to wrapped functions. + +This is exported so diagnostics can recognize memoized functions without +depending on a string property name. diff --git a/docs/reference/index/variables/aggregationFn_count.md b/docs/reference/index/variables/aggregationFn_count.md new file mode 100644 index 0000000000..0e0cda2e76 --- /dev/null +++ b/docs/reference/index/variables/aggregationFn_count.md @@ -0,0 +1,14 @@ +--- +id: aggregationFn_count +title: aggregationFn_count +--- + +# Variable: aggregationFn\_count + +```ts +const aggregationFn_count: AggregationFn; +``` + +Defined in: [fns/aggregationFns.ts:198](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/aggregationFns.ts#L198) + +Aggregation function for counting the number of rows in a column. diff --git a/docs/reference/index/variables/aggregationFn_extent.md b/docs/reference/index/variables/aggregationFn_extent.md new file mode 100644 index 0000000000..0bb994ef84 --- /dev/null +++ b/docs/reference/index/variables/aggregationFn_extent.md @@ -0,0 +1,14 @@ +--- +id: aggregationFn_extent +title: aggregationFn_extent +--- + +# Variable: aggregationFn\_extent + +```ts +const aggregationFn_extent: AggregationFn; +``` + +Defined in: [fns/aggregationFns.ts:84](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/aggregationFns.ts#L84) + +Aggregation function for finding the extent (min and max) of a column. diff --git a/docs/reference/index/variables/aggregationFn_max.md b/docs/reference/index/variables/aggregationFn_max.md new file mode 100644 index 0000000000..443b999581 --- /dev/null +++ b/docs/reference/index/variables/aggregationFn_max.md @@ -0,0 +1,14 @@ +--- +id: aggregationFn_max +title: aggregationFn_max +--- + +# Variable: aggregationFn\_max + +```ts +const aggregationFn_max: AggregationFn; +``` + +Defined in: [fns/aggregationFns.ts:57](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/aggregationFns.ts#L57) + +Aggregation function for finding the maximum value of a column. diff --git a/docs/reference/index/variables/aggregationFn_mean.md b/docs/reference/index/variables/aggregationFn_mean.md new file mode 100644 index 0000000000..321b066014 --- /dev/null +++ b/docs/reference/index/variables/aggregationFn_mean.md @@ -0,0 +1,14 @@ +--- +id: aggregationFn_mean +title: aggregationFn_mean +--- + +# Variable: aggregationFn\_mean + +```ts +const aggregationFn_mean: AggregationFn; +``` + +Defined in: [fns/aggregationFns.ts:113](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/aggregationFns.ts#L113) + +Aggregation function for finding the mean (average) of a column. diff --git a/docs/reference/index/variables/aggregationFn_median.md b/docs/reference/index/variables/aggregationFn_median.md new file mode 100644 index 0000000000..38a7925adb --- /dev/null +++ b/docs/reference/index/variables/aggregationFn_median.md @@ -0,0 +1,14 @@ +--- +id: aggregationFn_median +title: aggregationFn_median +--- + +# Variable: aggregationFn\_median + +```ts +const aggregationFn_median: AggregationFn; +``` + +Defined in: [fns/aggregationFns.ts:145](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/aggregationFns.ts#L145) + +Aggregation function for finding the median value of a column. diff --git a/docs/reference/index/variables/aggregationFn_min.md b/docs/reference/index/variables/aggregationFn_min.md new file mode 100644 index 0000000000..b5ceab3d37 --- /dev/null +++ b/docs/reference/index/variables/aggregationFn_min.md @@ -0,0 +1,14 @@ +--- +id: aggregationFn_min +title: aggregationFn_min +--- + +# Variable: aggregationFn\_min + +```ts +const aggregationFn_min: AggregationFn; +``` + +Defined in: [fns/aggregationFns.ts:29](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/aggregationFns.ts#L29) + +Aggregation function for finding the minimum value of a column. diff --git a/docs/reference/index/variables/aggregationFn_sum.md b/docs/reference/index/variables/aggregationFn_sum.md new file mode 100644 index 0000000000..aeece8ec0a --- /dev/null +++ b/docs/reference/index/variables/aggregationFn_sum.md @@ -0,0 +1,14 @@ +--- +id: aggregationFn_sum +title: aggregationFn_sum +--- + +# Variable: aggregationFn\_sum + +```ts +const aggregationFn_sum: AggregationFn; +``` + +Defined in: [fns/aggregationFns.ts:10](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/aggregationFns.ts#L10) + +Aggregation function for summing up the values of a column. diff --git a/docs/reference/index/variables/aggregationFn_unique.md b/docs/reference/index/variables/aggregationFn_unique.md new file mode 100644 index 0000000000..d04181e153 --- /dev/null +++ b/docs/reference/index/variables/aggregationFn_unique.md @@ -0,0 +1,14 @@ +--- +id: aggregationFn_unique +title: aggregationFn_unique +--- + +# Variable: aggregationFn\_unique + +```ts +const aggregationFn_unique: AggregationFn; +``` + +Defined in: [fns/aggregationFns.ts:172](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/aggregationFns.ts#L172) + +Aggregation function for finding the unique values of a column. diff --git a/docs/reference/index/variables/aggregationFn_uniqueCount.md b/docs/reference/index/variables/aggregationFn_uniqueCount.md new file mode 100644 index 0000000000..f45e125c82 --- /dev/null +++ b/docs/reference/index/variables/aggregationFn_uniqueCount.md @@ -0,0 +1,14 @@ +--- +id: aggregationFn_uniqueCount +title: aggregationFn_uniqueCount +--- + +# Variable: aggregationFn\_uniqueCount + +```ts +const aggregationFn_uniqueCount: AggregationFn; +``` + +Defined in: [fns/aggregationFns.ts:185](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/aggregationFns.ts#L185) + +Aggregation function for finding the count of unique values of a column. diff --git a/docs/reference/index/variables/aggregationFns.md b/docs/reference/index/variables/aggregationFns.md new file mode 100644 index 0000000000..55549b6a84 --- /dev/null +++ b/docs/reference/index/variables/aggregationFns.md @@ -0,0 +1,72 @@ +--- +id: aggregationFns +title: aggregationFns +--- + +# Variable: aggregationFns + +```ts +const aggregationFns: object; +``` + +Defined in: [fns/aggregationFns.ts:213](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/aggregationFns.ts#L213) + +The built-in aggregation function registry. + +Pass this object to grouped row model creation or extend it with custom aggregation functions for grouped columns. + +## Type Declaration + +### count + +```ts +count: AggregationFn = aggregationFn_count; +``` + +### extent + +```ts +extent: AggregationFn = aggregationFn_extent; +``` + +### max + +```ts +max: AggregationFn = aggregationFn_max; +``` + +### mean + +```ts +mean: AggregationFn = aggregationFn_mean; +``` + +### median + +```ts +median: AggregationFn = aggregationFn_median; +``` + +### min + +```ts +min: AggregationFn = aggregationFn_min; +``` + +### sum + +```ts +sum: AggregationFn = aggregationFn_sum; +``` + +### unique + +```ts +unique: AggregationFn = aggregationFn_unique; +``` + +### uniqueCount + +```ts +uniqueCount: AggregationFn = aggregationFn_uniqueCount; +``` diff --git a/docs/reference/index/variables/columnFacetingFeature.md b/docs/reference/index/variables/columnFacetingFeature.md new file mode 100644 index 0000000000..e3f159a93c --- /dev/null +++ b/docs/reference/index/variables/columnFacetingFeature.md @@ -0,0 +1,17 @@ +--- +id: columnFacetingFeature +title: columnFacetingFeature +--- + +# Variable: columnFacetingFeature + +```ts +const columnFacetingFeature: TableFeature>; +``` + +Defined in: [features/column-faceting/columnFacetingFeature.ts:120](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.ts#L120) + +The stock column faceting feature. + +Register this feature to add faceted row model, unique value, and min/max +helpers for column and global filter UIs. diff --git a/docs/reference/index/variables/columnFilteringFeature.md b/docs/reference/index/variables/columnFilteringFeature.md new file mode 100644 index 0000000000..71cd1558b4 --- /dev/null +++ b/docs/reference/index/variables/columnFilteringFeature.md @@ -0,0 +1,18 @@ +--- +id: columnFilteringFeature +title: columnFilteringFeature +--- + +# Variable: columnFilteringFeature + +```ts +const columnFilteringFeature: TableFeature>; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.ts:129](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.ts#L129) + +The stock column filtering feature. + +Register this feature to add column filter state, filter defaults, and +table/row/column APIs for client-side or manual column filtering. Global +filtering is provided by `globalFilteringFeature`. diff --git a/docs/reference/index/variables/columnGroupingFeature.md b/docs/reference/index/variables/columnGroupingFeature.md new file mode 100644 index 0000000000..0f11105206 --- /dev/null +++ b/docs/reference/index/variables/columnGroupingFeature.md @@ -0,0 +1,17 @@ +--- +id: columnGroupingFeature +title: columnGroupingFeature +--- + +# Variable: columnGroupingFeature + +```ts +const columnGroupingFeature: TableFeature>; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.ts:159](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.ts#L159) + +The stock column grouping feature. + +Register this feature to add grouping state, aggregation defaults, grouped +row-model support, and table/row/column/cell grouping APIs. diff --git a/docs/reference/index/variables/columnOrderingFeature.md b/docs/reference/index/variables/columnOrderingFeature.md new file mode 100644 index 0000000000..0ab348db4e --- /dev/null +++ b/docs/reference/index/variables/columnOrderingFeature.md @@ -0,0 +1,17 @@ +--- +id: columnOrderingFeature +title: columnOrderingFeature +--- + +# Variable: columnOrderingFeature + +```ts +const columnOrderingFeature: TableFeature>; +``` + +Defined in: [features/column-ordering/columnOrderingFeature.ts:104](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-ordering/columnOrderingFeature.ts#L104) + +The stock column ordering feature. + +Register this feature to add column order state and APIs for deriving the +ordered leaf column list alongside grouping and pinning. diff --git a/docs/reference/index/variables/columnPinningFeature.md b/docs/reference/index/variables/columnPinningFeature.md new file mode 100644 index 0000000000..a4d0725b01 --- /dev/null +++ b/docs/reference/index/variables/columnPinningFeature.md @@ -0,0 +1,17 @@ +--- +id: columnPinningFeature +title: columnPinningFeature +--- + +# Variable: columnPinningFeature + +```ts +const columnPinningFeature: TableFeature>; +``` + +Defined in: [features/column-pinning/columnPinningFeature.ts:355](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.ts#L355) + +The stock column pinning feature. + +Register this feature to add column pinning state plus table, row, and column +APIs for splitting columns into left, center, and right regions. diff --git a/docs/reference/index/variables/columnResizingFeature.md b/docs/reference/index/variables/columnResizingFeature.md new file mode 100644 index 0000000000..a649154d58 --- /dev/null +++ b/docs/reference/index/variables/columnResizingFeature.md @@ -0,0 +1,17 @@ +--- +id: columnResizingFeature +title: columnResizingFeature +--- + +# Variable: columnResizingFeature + +```ts +const columnResizingFeature: TableFeature>; +``` + +Defined in: [features/column-resizing/columnResizingFeature.ts:99](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.ts#L99) + +The stock column resizing feature. + +Register this feature with `columnSizingFeature` to add resize interaction +state and APIs for drag-based column resizing. diff --git a/docs/reference/index/variables/columnSizingFeature.md b/docs/reference/index/variables/columnSizingFeature.md new file mode 100644 index 0000000000..5fd1433ba2 --- /dev/null +++ b/docs/reference/index/variables/columnSizingFeature.md @@ -0,0 +1,18 @@ +--- +id: columnSizingFeature +title: columnSizingFeature +--- + +# Variable: columnSizingFeature + +```ts +const columnSizingFeature: TableFeature>; +``` + +Defined in: [features/column-sizing/columnSizingFeature.ts:154](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.ts#L154) + +The stock column sizing feature. + +Register this feature to add column width state and table, header, and column +APIs for reading and resetting sizes. Column drag resizing lives in +`columnResizingFeature`. diff --git a/docs/reference/index/variables/columnVisibilityFeature.md b/docs/reference/index/variables/columnVisibilityFeature.md new file mode 100644 index 0000000000..070421f4d3 --- /dev/null +++ b/docs/reference/index/variables/columnVisibilityFeature.md @@ -0,0 +1,17 @@ +--- +id: columnVisibilityFeature +title: columnVisibilityFeature +--- + +# Variable: columnVisibilityFeature + +```ts +const columnVisibilityFeature: TableFeature>; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.ts:165](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.ts#L165) + +The stock column visibility feature. + +Register this feature to add column visibility state and APIs for deriving +visible columns and visible row cells. diff --git a/docs/reference/index/variables/coreCellsFeature.md b/docs/reference/index/variables/coreCellsFeature.md new file mode 100644 index 0000000000..67a9033121 --- /dev/null +++ b/docs/reference/index/variables/coreCellsFeature.md @@ -0,0 +1,14 @@ +--- +id: coreCellsFeature +title: coreCellsFeature +--- + +# Variable: coreCellsFeature + +```ts +const coreCellsFeature: TableFeature>; +``` + +Defined in: [core/cells/coreCellsFeature.ts:49](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.ts#L49) + +The Core Cells feature provides the core cell functionality. diff --git a/docs/reference/index/variables/coreColumnsFeature.md b/docs/reference/index/variables/coreColumnsFeature.md new file mode 100644 index 0000000000..914f8a0863 --- /dev/null +++ b/docs/reference/index/variables/coreColumnsFeature.md @@ -0,0 +1,14 @@ +--- +id: coreColumnsFeature +title: coreColumnsFeature +--- + +# Variable: coreColumnsFeature + +```ts +const coreColumnsFeature: TableFeature>; +``` + +Defined in: [core/columns/coreColumnsFeature.ts:95](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.ts#L95) + +The Core Columns feature provides the core column functionality. diff --git a/docs/reference/index/variables/coreFeatures.md b/docs/reference/index/variables/coreFeatures.md new file mode 100644 index 0000000000..3e6f65e194 --- /dev/null +++ b/docs/reference/index/variables/coreFeatures.md @@ -0,0 +1,16 @@ +--- +id: coreFeatures +title: coreFeatures +--- + +# Variable: coreFeatures + +```ts +const coreFeatures: CoreFeatures; +``` + +Defined in: [core/coreFeatures.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/coreFeatures.ts#L24) + +The built-in core feature set required by every table. + +These features provide table, column, row, header, cell, and core row-model behavior before optional feature plugins are added. diff --git a/docs/reference/index/variables/coreHeadersFeature.md b/docs/reference/index/variables/coreHeadersFeature.md new file mode 100644 index 0000000000..5d9294526a --- /dev/null +++ b/docs/reference/index/variables/coreHeadersFeature.md @@ -0,0 +1,14 @@ +--- +id: coreHeadersFeature +title: coreHeadersFeature +--- + +# Variable: coreHeadersFeature + +```ts +const coreHeadersFeature: TableFeature>; +``` + +Defined in: [core/headers/coreHeadersFeature.ts:103](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.ts#L103) + +The Core Headers feature provides the core header functionality. diff --git a/docs/reference/index/variables/coreRowModelsFeature.md b/docs/reference/index/variables/coreRowModelsFeature.md new file mode 100644 index 0000000000..9eee100686 --- /dev/null +++ b/docs/reference/index/variables/coreRowModelsFeature.md @@ -0,0 +1,14 @@ +--- +id: coreRowModelsFeature +title: coreRowModelsFeature +--- + +# Variable: coreRowModelsFeature + +```ts +const coreRowModelsFeature: TableFeature>; +``` + +Defined in: [core/row-models/coreRowModelsFeature.ts:83](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.ts#L83) + +The Core Row Models feature provides the core row model functionality. diff --git a/docs/reference/index/variables/coreRowsFeature.md b/docs/reference/index/variables/coreRowsFeature.md new file mode 100644 index 0000000000..2573b20c99 --- /dev/null +++ b/docs/reference/index/variables/coreRowsFeature.md @@ -0,0 +1,14 @@ +--- +id: coreRowsFeature +title: coreRowsFeature +--- + +# Variable: coreRowsFeature + +```ts +const coreRowsFeature: TableFeature>; +``` + +Defined in: [core/rows/coreRowsFeature.ts:89](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.ts#L89) + +The Core Rows feature provides the core row functionality. diff --git a/docs/reference/index/variables/coreTablesFeature.md b/docs/reference/index/variables/coreTablesFeature.md new file mode 100644 index 0000000000..d67579c562 --- /dev/null +++ b/docs/reference/index/variables/coreTablesFeature.md @@ -0,0 +1,14 @@ +--- +id: coreTablesFeature +title: coreTablesFeature +--- + +# Variable: coreTablesFeature + +```ts +const coreTablesFeature: TableFeature>; +``` + +Defined in: [core/table/coreTablesFeature.ts:41](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.ts#L41) + +The Core Tables feature provides the core table functionality for handling state and options. diff --git a/docs/reference/index/variables/filterFn_arrHas.md b/docs/reference/index/variables/filterFn_arrHas.md new file mode 100644 index 0000000000..09c7f6330e --- /dev/null +++ b/docs/reference/index/variables/filterFn_arrHas.md @@ -0,0 +1,14 @@ +--- +id: filterFn_arrHas +title: filterFn_arrHas +--- + +# Variable: filterFn\_arrHas + +```ts +const filterFn_arrHas: FilterFn; +``` + +Defined in: [fns/filterFns.ts:287](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L287) + +Filter function for checking if an array has a given value. diff --git a/docs/reference/index/variables/filterFn_arrIncludes.md b/docs/reference/index/variables/filterFn_arrIncludes.md new file mode 100644 index 0000000000..7ef63136e6 --- /dev/null +++ b/docs/reference/index/variables/filterFn_arrIncludes.md @@ -0,0 +1,14 @@ +--- +id: filterFn_arrIncludes +title: filterFn_arrIncludes +--- + +# Variable: filterFn\_arrIncludes + +```ts +const filterFn_arrIncludes: FilterFn; +``` + +Defined in: [fns/filterFns.ts:301](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L301) + +Filter function for checking if an array includes a given value. diff --git a/docs/reference/index/variables/filterFn_arrIncludesAll.md b/docs/reference/index/variables/filterFn_arrIncludesAll.md new file mode 100644 index 0000000000..ba14ab386c --- /dev/null +++ b/docs/reference/index/variables/filterFn_arrIncludesAll.md @@ -0,0 +1,14 @@ +--- +id: filterFn_arrIncludesAll +title: filterFn_arrIncludesAll +--- + +# Variable: filterFn\_arrIncludesAll + +```ts +const filterFn_arrIncludesAll: FilterFn; +``` + +Defined in: [fns/filterFns.ts:321](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L321) + +Filter function for checking if an array includes all of the given values. diff --git a/docs/reference/index/variables/filterFn_arrIncludesSome.md b/docs/reference/index/variables/filterFn_arrIncludesSome.md new file mode 100644 index 0000000000..3f583c3da2 --- /dev/null +++ b/docs/reference/index/variables/filterFn_arrIncludesSome.md @@ -0,0 +1,14 @@ +--- +id: filterFn_arrIncludesSome +title: filterFn_arrIncludesSome +--- + +# Variable: filterFn\_arrIncludesSome + +```ts +const filterFn_arrIncludesSome: FilterFn; +``` + +Defined in: [fns/filterFns.ts:340](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L340) + +Filter function for checking if an array includes any of the given values. diff --git a/docs/reference/index/variables/filterFn_equals.md b/docs/reference/index/variables/filterFn_equals.md new file mode 100644 index 0000000000..ff3f1adaf8 --- /dev/null +++ b/docs/reference/index/variables/filterFn_equals.md @@ -0,0 +1,14 @@ +--- +id: filterFn_equals +title: filterFn_equals +--- + +# Variable: filterFn\_equals + +```ts +const filterFn_equals: FilterFn; +``` + +Defined in: [fns/filterFns.ts:11](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L11) + +Filter function for checking if a value is exactly equal to a given value. (JS === comparison) diff --git a/docs/reference/index/variables/filterFn_equalsString.md b/docs/reference/index/variables/filterFn_equalsString.md new file mode 100644 index 0000000000..f7feb8f0ce --- /dev/null +++ b/docs/reference/index/variables/filterFn_equalsString.md @@ -0,0 +1,14 @@ +--- +id: filterFn_equalsString +title: filterFn_equalsString +--- + +# Variable: filterFn\_equalsString + +```ts +const filterFn_equalsString: FilterFn; +``` + +Defined in: [fns/filterFns.ts:85](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L85) + +Filter function for checking if a string is exactly equal to a given string. (Non-case-sensitive) diff --git a/docs/reference/index/variables/filterFn_equalsStringSensitive.md b/docs/reference/index/variables/filterFn_equalsStringSensitive.md new file mode 100644 index 0000000000..2dd960758c --- /dev/null +++ b/docs/reference/index/variables/filterFn_equalsStringSensitive.md @@ -0,0 +1,14 @@ +--- +id: filterFn_equalsStringSensitive +title: filterFn_equalsStringSensitive +--- + +# Variable: filterFn\_equalsStringSensitive + +```ts +const filterFn_equalsStringSensitive: FilterFn; +``` + +Defined in: [fns/filterFns.ts:104](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L104) + +Filter function for checking if a string is exactly equal to a given string. (Case-sensitive) diff --git a/docs/reference/index/variables/filterFn_greaterThan.md b/docs/reference/index/variables/filterFn_greaterThan.md new file mode 100644 index 0000000000..c1a86a2c1d --- /dev/null +++ b/docs/reference/index/variables/filterFn_greaterThan.md @@ -0,0 +1,14 @@ +--- +id: filterFn_greaterThan +title: filterFn_greaterThan +--- + +# Variable: filterFn\_greaterThan + +```ts +const filterFn_greaterThan: FilterFn; +``` + +Defined in: [fns/filterFns.ts:122](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L122) + +Filter function for checking if a number is greater than a given number. diff --git a/docs/reference/index/variables/filterFn_greaterThanOrEqualTo.md b/docs/reference/index/variables/filterFn_greaterThanOrEqualTo.md new file mode 100644 index 0000000000..513c1e07cc --- /dev/null +++ b/docs/reference/index/variables/filterFn_greaterThanOrEqualTo.md @@ -0,0 +1,14 @@ +--- +id: filterFn_greaterThanOrEqualTo +title: filterFn_greaterThanOrEqualTo +--- + +# Variable: filterFn\_greaterThanOrEqualTo + +```ts +const filterFn_greaterThanOrEqualTo: FilterFn; +``` + +Defined in: [fns/filterFns.ts:149](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L149) + +Filter function for checking if a number is greater than or equal to a given number. diff --git a/docs/reference/index/variables/filterFn_inNumberRange.md b/docs/reference/index/variables/filterFn_inNumberRange.md new file mode 100644 index 0000000000..363992954d --- /dev/null +++ b/docs/reference/index/variables/filterFn_inNumberRange.md @@ -0,0 +1,14 @@ +--- +id: filterFn_inNumberRange +title: filterFn_inNumberRange +--- + +# Variable: filterFn\_inNumberRange + +```ts +const filterFn_inNumberRange: FilterFn; +``` + +Defined in: [fns/filterFns.ts:244](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L244) + +Filter function for checking if a number is within a given range. diff --git a/docs/reference/index/variables/filterFn_includesString.md b/docs/reference/index/variables/filterFn_includesString.md new file mode 100644 index 0000000000..ac378ce25d --- /dev/null +++ b/docs/reference/index/variables/filterFn_includesString.md @@ -0,0 +1,14 @@ +--- +id: filterFn_includesString +title: filterFn_includesString +--- + +# Variable: filterFn\_includesString + +```ts +const filterFn_includesString: FilterFn; +``` + +Defined in: [fns/filterFns.ts:63](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L63) + +Filter function for checking if a string includes a given substring. (Non-case-sensitive) diff --git a/docs/reference/index/variables/filterFn_includesStringSensitive.md b/docs/reference/index/variables/filterFn_includesStringSensitive.md new file mode 100644 index 0000000000..244397153f --- /dev/null +++ b/docs/reference/index/variables/filterFn_includesStringSensitive.md @@ -0,0 +1,14 @@ +--- +id: filterFn_includesStringSensitive +title: filterFn_includesStringSensitive +--- + +# Variable: filterFn\_includesStringSensitive + +```ts +const filterFn_includesStringSensitive: FilterFn; +``` + +Defined in: [fns/filterFns.ts:45](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L45) + +Filter function for checking if a string includes a given substring. (Case-sensitive) diff --git a/docs/reference/index/variables/filterFn_lessThan.md b/docs/reference/index/variables/filterFn_lessThan.md new file mode 100644 index 0000000000..3917d74e60 --- /dev/null +++ b/docs/reference/index/variables/filterFn_lessThan.md @@ -0,0 +1,14 @@ +--- +id: filterFn_lessThan +title: filterFn_lessThan +--- + +# Variable: filterFn\_lessThan + +```ts +const filterFn_lessThan: FilterFn; +``` + +Defined in: [fns/filterFns.ts:168](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L168) + +Filter function for checking if a number is less than a given number. diff --git a/docs/reference/index/variables/filterFn_lessThanOrEqualTo.md b/docs/reference/index/variables/filterFn_lessThanOrEqualTo.md new file mode 100644 index 0000000000..7febfe8575 --- /dev/null +++ b/docs/reference/index/variables/filterFn_lessThanOrEqualTo.md @@ -0,0 +1,14 @@ +--- +id: filterFn_lessThanOrEqualTo +title: filterFn_lessThanOrEqualTo +--- + +# Variable: filterFn\_lessThanOrEqualTo + +```ts +const filterFn_lessThanOrEqualTo: FilterFn; +``` + +Defined in: [fns/filterFns.ts:184](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L184) + +Filter function for checking if a number is less than or equal to a given number. diff --git a/docs/reference/index/variables/filterFn_weakEquals.md b/docs/reference/index/variables/filterFn_weakEquals.md new file mode 100644 index 0000000000..44c176be0a --- /dev/null +++ b/docs/reference/index/variables/filterFn_weakEquals.md @@ -0,0 +1,14 @@ +--- +id: filterFn_weakEquals +title: filterFn_weakEquals +--- + +# Variable: filterFn\_weakEquals + +```ts +const filterFn_weakEquals: FilterFn; +``` + +Defined in: [fns/filterFns.ts:27](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L27) + +Filter function for checking if a value is weakly equal to a given value. (JS == comparison) diff --git a/docs/reference/index/variables/filterFns.md b/docs/reference/index/variables/filterFns.md new file mode 100644 index 0000000000..6e78497eb6 --- /dev/null +++ b/docs/reference/index/variables/filterFns.md @@ -0,0 +1,90 @@ +--- +id: filterFns +title: filterFns +--- + +# Variable: filterFns + +```ts +const filterFns: object; +``` + +Defined in: [fns/filterFns.ts:363](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/filterFns.ts#L363) + +The built-in filter function registry. + +Pass this object to filtered row model creation or extend it with custom filter functions. + +## Type Declaration + +### arrHas + +```ts +arrHas: FilterFn = filterFn_arrHas; +``` + +### arrIncludes + +```ts +arrIncludes: FilterFn = filterFn_arrIncludes; +``` + +### arrIncludesAll + +```ts +arrIncludesAll: FilterFn = filterFn_arrIncludesAll; +``` + +### arrIncludesSome + +```ts +arrIncludesSome: FilterFn = filterFn_arrIncludesSome; +``` + +### between + +```ts +between: FilterFn = filterFn_between; +``` + +### betweenInclusive + +```ts +betweenInclusive: FilterFn = filterFn_betweenInclusive; +``` + +### equals + +```ts +equals: FilterFn = filterFn_equals; +``` + +### equalsString + +```ts +equalsString: FilterFn = filterFn_equalsString; +``` + +### includesString + +```ts +includesString: FilterFn = filterFn_includesString; +``` + +### includesStringSensitive + +```ts +includesStringSensitive: FilterFn = filterFn_includesStringSensitive; +``` + +### inNumberRange + +```ts +inNumberRange: FilterFn = filterFn_inNumberRange; +``` + +### weakEquals + +```ts +weakEquals: FilterFn = filterFn_weakEquals; +``` diff --git a/docs/reference/index/variables/globalFilteringFeature.md b/docs/reference/index/variables/globalFilteringFeature.md new file mode 100644 index 0000000000..6209716142 --- /dev/null +++ b/docs/reference/index/variables/globalFilteringFeature.md @@ -0,0 +1,17 @@ +--- +id: globalFilteringFeature +title: globalFilteringFeature +--- + +# Variable: globalFilteringFeature + +```ts +const globalFilteringFeature: TableFeature>; +``` + +Defined in: [features/global-filtering/globalFilteringFeature.ts:99](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.ts#L99) + +The stock global filtering feature. + +Register this feature with column filtering support to add global filter +state, global filter function resolution, and column eligibility APIs. diff --git a/docs/reference/index/variables/reSplitAlphaNumeric.md b/docs/reference/index/variables/reSplitAlphaNumeric.md new file mode 100644 index 0000000000..56f11267f8 --- /dev/null +++ b/docs/reference/index/variables/reSplitAlphaNumeric.md @@ -0,0 +1,17 @@ +--- +id: reSplitAlphaNumeric +title: reSplitAlphaNumeric +--- + +# Variable: reSplitAlphaNumeric + +```ts +const reSplitAlphaNumeric: RegExp; +``` + +Defined in: [fns/sortFns.ts:12](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/sortFns.ts#L12) + +Regular expression used to split mixed text and numeric chunks. + +The alphanumeric sort functions use these chunks for natural sorting of +strings like `item2` before `item10`. diff --git a/docs/reference/index/variables/rowExpandingFeature.md b/docs/reference/index/variables/rowExpandingFeature.md new file mode 100644 index 0000000000..e401f36004 --- /dev/null +++ b/docs/reference/index/variables/rowExpandingFeature.md @@ -0,0 +1,17 @@ +--- +id: rowExpandingFeature +title: rowExpandingFeature +--- + +# Variable: rowExpandingFeature + +```ts +const rowExpandingFeature: TableFeature>; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.ts:130](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.ts#L130) + +The stock row expanding feature. + +Register this feature to add expanded row state, row expansion APIs, and +helpers for deriving expanded row models and expansion depth. diff --git a/docs/reference/index/variables/rowPaginationFeature.md b/docs/reference/index/variables/rowPaginationFeature.md new file mode 100644 index 0000000000..ac4d4e8cab --- /dev/null +++ b/docs/reference/index/variables/rowPaginationFeature.md @@ -0,0 +1,17 @@ +--- +id: rowPaginationFeature +title: rowPaginationFeature +--- + +# Variable: rowPaginationFeature + +```ts +const rowPaginationFeature: TableFeature>; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.ts:129](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.ts#L129) + +The stock row pagination feature. + +Register this feature to add pagination state, page navigation APIs, and +pagination-aware row model helpers to a table. diff --git a/docs/reference/index/variables/rowPinningFeature.md b/docs/reference/index/variables/rowPinningFeature.md new file mode 100644 index 0000000000..309fdab8ae --- /dev/null +++ b/docs/reference/index/variables/rowPinningFeature.md @@ -0,0 +1,17 @@ +--- +id: rowPinningFeature +title: rowPinningFeature +--- + +# Variable: rowPinningFeature + +```ts +const rowPinningFeature: TableFeature>; +``` + +Defined in: [features/row-pinning/rowPinningFeature.ts:129](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.ts#L129) + +The stock row pinning feature. + +Register this feature to add row pinning state and APIs for deriving top, +center, and bottom row regions. diff --git a/docs/reference/index/variables/rowSelectionFeature.md b/docs/reference/index/variables/rowSelectionFeature.md new file mode 100644 index 0000000000..87e8e539ae --- /dev/null +++ b/docs/reference/index/variables/rowSelectionFeature.md @@ -0,0 +1,17 @@ +--- +id: rowSelectionFeature +title: rowSelectionFeature +--- + +# Variable: rowSelectionFeature + +```ts +const rowSelectionFeature: TableFeature>; +``` + +Defined in: [features/row-selection/rowSelectionFeature.ts:172](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.ts#L172) + +The stock row selection feature. + +Register this feature to add row selection state, selected row-model helpers, +and table/row APIs for checkbox and bulk-selection UI. diff --git a/docs/reference/index/variables/rowSortingFeature.md b/docs/reference/index/variables/rowSortingFeature.md new file mode 100644 index 0000000000..9f43b6948d --- /dev/null +++ b/docs/reference/index/variables/rowSortingFeature.md @@ -0,0 +1,17 @@ +--- +id: rowSortingFeature +title: rowSortingFeature +--- + +# Variable: rowSortingFeature + +```ts +const rowSortingFeature: TableFeature>; +``` + +Defined in: [features/row-sorting/rowSortingFeature.ts:144](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.ts#L144) + +The stock row sorting feature. + +Register this feature to add sorting state, column sorting APIs, and sorted +row-model support for client-side or manual sorting. diff --git a/docs/reference/index/variables/sortFn_alphanumeric.md b/docs/reference/index/variables/sortFn_alphanumeric.md new file mode 100644 index 0000000000..d2d3852901 --- /dev/null +++ b/docs/reference/index/variables/sortFn_alphanumeric.md @@ -0,0 +1,16 @@ +--- +id: sortFn_alphanumeric +title: sortFn_alphanumeric +--- + +# Variable: sortFn\_alphanumeric + +```ts +const sortFn_alphanumeric: SortFn; +``` + +Defined in: [fns/sortFns.ts:19](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/sortFns.ts#L19) + +Sorts rows with the built-in alphanumeric strategy. + +This comparator returns ascending-order results; descending order is applied by the sorting row model. diff --git a/docs/reference/index/variables/sortFn_alphanumericCaseSensitive.md b/docs/reference/index/variables/sortFn_alphanumericCaseSensitive.md new file mode 100644 index 0000000000..bd96055c78 --- /dev/null +++ b/docs/reference/index/variables/sortFn_alphanumericCaseSensitive.md @@ -0,0 +1,16 @@ +--- +id: sortFn_alphanumericCaseSensitive +title: sortFn_alphanumericCaseSensitive +--- + +# Variable: sortFn\_alphanumericCaseSensitive + +```ts +const sortFn_alphanumericCaseSensitive: SortFn; +``` + +Defined in: [fns/sortFns.ts:38](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/sortFns.ts#L38) + +Sorts rows with the built-in alphanumeric case sensitive strategy. + +This comparator returns ascending-order results; descending order is applied by the sorting row model. diff --git a/docs/reference/index/variables/sortFn_basic.md b/docs/reference/index/variables/sortFn_basic.md new file mode 100644 index 0000000000..14348318bb --- /dev/null +++ b/docs/reference/index/variables/sortFn_basic.md @@ -0,0 +1,16 @@ +--- +id: sortFn_basic +title: sortFn_basic +--- + +# Variable: sortFn\_basic + +```ts +const sortFn_basic: SortFn; +``` + +Defined in: [fns/sortFns.ts:121](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/sortFns.ts#L121) + +Sorts rows with the built-in basic strategy. + +This comparator returns ascending-order results; descending order is applied by the sorting row model. diff --git a/docs/reference/index/variables/sortFn_datetime.md b/docs/reference/index/variables/sortFn_datetime.md new file mode 100644 index 0000000000..604db2b469 --- /dev/null +++ b/docs/reference/index/variables/sortFn_datetime.md @@ -0,0 +1,16 @@ +--- +id: sortFn_datetime +title: sortFn_datetime +--- + +# Variable: sortFn\_datetime + +```ts +const sortFn_datetime: SortFn; +``` + +Defined in: [fns/sortFns.ts:99](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/sortFns.ts#L99) + +Sorts rows with the built-in datetime strategy. + +This comparator returns ascending-order results; descending order is applied by the sorting row model. diff --git a/docs/reference/index/variables/sortFn_text.md b/docs/reference/index/variables/sortFn_text.md new file mode 100644 index 0000000000..53ed9f46ba --- /dev/null +++ b/docs/reference/index/variables/sortFn_text.md @@ -0,0 +1,16 @@ +--- +id: sortFn_text +title: sortFn_text +--- + +# Variable: sortFn\_text + +```ts +const sortFn_text: SortFn; +``` + +Defined in: [fns/sortFns.ts:59](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/sortFns.ts#L59) + +Sorts rows with the built-in text strategy. + +This comparator returns ascending-order results; descending order is applied by the sorting row model. diff --git a/docs/reference/index/variables/sortFn_textCaseSensitive.md b/docs/reference/index/variables/sortFn_textCaseSensitive.md new file mode 100644 index 0000000000..513234dccb --- /dev/null +++ b/docs/reference/index/variables/sortFn_textCaseSensitive.md @@ -0,0 +1,16 @@ +--- +id: sortFn_textCaseSensitive +title: sortFn_textCaseSensitive +--- + +# Variable: sortFn\_textCaseSensitive + +```ts +const sortFn_textCaseSensitive: SortFn; +``` + +Defined in: [fns/sortFns.ts:80](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/sortFns.ts#L80) + +Sorts rows with the built-in text case sensitive strategy. + +This comparator returns ascending-order results; descending order is applied by the sorting row model. diff --git a/docs/reference/index/variables/sortFns.md b/docs/reference/index/variables/sortFns.md new file mode 100644 index 0000000000..64a351e5cf --- /dev/null +++ b/docs/reference/index/variables/sortFns.md @@ -0,0 +1,54 @@ +--- +id: sortFns +title: sortFns +--- + +# Variable: sortFns + +```ts +const sortFns: object; +``` + +Defined in: [fns/sortFns.ts:205](https://github.com/TanStack/table/blob/main/packages/table-core/src/fns/sortFns.ts#L205) + +The built-in sorting function registry. + +Pass this object to sorted row model creation or extend it with custom sorting functions. + +## Type Declaration + +### alphanumeric + +```ts +alphanumeric: SortFn = sortFn_alphanumeric; +``` + +### alphanumericCaseSensitive + +```ts +alphanumericCaseSensitive: SortFn = sortFn_alphanumericCaseSensitive; +``` + +### basic + +```ts +basic: SortFn = sortFn_basic; +``` + +### datetime + +```ts +datetime: SortFn = sortFn_datetime; +``` + +### text + +```ts +text: SortFn = sortFn_text; +``` + +### textCaseSensitive + +```ts +textCaseSensitive: SortFn = sortFn_textCaseSensitive; +``` diff --git a/docs/reference/index/variables/stockFeatures.md b/docs/reference/index/variables/stockFeatures.md new file mode 100644 index 0000000000..7cc27311ff --- /dev/null +++ b/docs/reference/index/variables/stockFeatures.md @@ -0,0 +1,16 @@ +--- +id: stockFeatures +title: stockFeatures +--- + +# Variable: stockFeatures + +```ts +const stockFeatures: StockFeatures; +``` + +Defined in: [features/stockFeatures.ts:38](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/stockFeatures.ts#L38) + +The complete set of stock optional table features. + +Use individual feature exports for tree-shaking, or this aggregate when a table should include every built-in feature. diff --git a/docs/reference/static-functions/functions/cell_getContext.md b/docs/reference/static-functions/functions/cell_getContext.md new file mode 100644 index 0000000000..287ceaeb17 --- /dev/null +++ b/docs/reference/static-functions/functions/cell_getContext.md @@ -0,0 +1,90 @@ +--- +id: cell_getContext +title: cell_getContext +--- + +# Function: cell\_getContext() + +```ts +function cell_getContext(cell): object; +``` + +Defined in: [core/cells/coreCellsFeature.utils.ts:51](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.utils.ts#L51) + +Returns context for a cell. + +This is the static implementation behind the matching cell instance API and uses the owning row and column context. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### cell + +[`Cell`](../../index/type-aliases/Cell.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`object` + +### cell + +```ts +cell: Cell; +``` + +### column + +```ts +column: Column = cell.column; +``` + +### getValue() + +```ts +getValue: () => NoInfer; +``` + +#### Returns + +[`NoInfer`](../../index/type-aliases/NoInfer.md)\<`TValue`\> + +### renderValue() + +```ts +renderValue: () => NoInfer; +``` + +#### Returns + +[`NoInfer`](../../index/type-aliases/NoInfer.md)\<`TValue` \| `null`\> + +### row + +```ts +row: Row = cell.row; +``` + +### table + +```ts +table: Table_Internal = cell.table; +``` + +## Example + +```ts +const value = cell_getContext(cell) +``` diff --git a/docs/reference/static-functions/functions/cell_getIsAggregated.md b/docs/reference/static-functions/functions/cell_getIsAggregated.md new file mode 100644 index 0000000000..c808d370f8 --- /dev/null +++ b/docs/reference/static-functions/functions/cell_getIsAggregated.md @@ -0,0 +1,46 @@ +--- +id: cell_getIsAggregated +title: cell_getIsAggregated +--- + +# Function: cell\_getIsAggregated() + +```ts +function cell_getIsAggregated(cell): boolean; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:333](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L333) + +Returns is aggregated for a cell. + +This is the static implementation behind the matching cell instance API and uses the owning row and column context. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### cell + +[`Cell`](../../index/type-aliases/Cell.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = cell_getIsAggregated(cell) +``` diff --git a/docs/reference/static-functions/functions/cell_getIsGrouped.md b/docs/reference/static-functions/functions/cell_getIsGrouped.md new file mode 100644 index 0000000000..4ab3c7c65a --- /dev/null +++ b/docs/reference/static-functions/functions/cell_getIsGrouped.md @@ -0,0 +1,46 @@ +--- +id: cell_getIsGrouped +title: cell_getIsGrouped +--- + +# Function: cell\_getIsGrouped() + +```ts +function cell_getIsGrouped(cell): boolean; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:294](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L294) + +Returns is grouped for a cell. + +This is the static implementation behind the matching cell instance API and uses the owning row and column context. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### cell + +[`Cell`](../../index/type-aliases/Cell.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = cell_getIsGrouped(cell) +``` diff --git a/docs/reference/static-functions/functions/cell_getIsPlaceholder.md b/docs/reference/static-functions/functions/cell_getIsPlaceholder.md new file mode 100644 index 0000000000..c3db245219 --- /dev/null +++ b/docs/reference/static-functions/functions/cell_getIsPlaceholder.md @@ -0,0 +1,46 @@ +--- +id: cell_getIsPlaceholder +title: cell_getIsPlaceholder +--- + +# Function: cell\_getIsPlaceholder() + +```ts +function cell_getIsPlaceholder(cell): boolean; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:315](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L315) + +Returns is placeholder for a cell. + +This is the static implementation behind the matching cell instance API and uses the owning row and column context. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### cell + +[`Cell`](../../index/type-aliases/Cell.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = cell_getIsPlaceholder(cell) +``` diff --git a/docs/reference/static-functions/functions/cell_getValue.md b/docs/reference/static-functions/functions/cell_getValue.md new file mode 100644 index 0000000000..015592ab65 --- /dev/null +++ b/docs/reference/static-functions/functions/cell_getValue.md @@ -0,0 +1,46 @@ +--- +id: cell_getValue +title: cell_getValue +--- + +# Function: cell\_getValue() + +```ts +function cell_getValue(cell): TValue; +``` + +Defined in: [core/cells/coreCellsFeature.utils.ts:15](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.utils.ts#L15) + +Returns value for a cell. + +This is the static implementation behind the matching cell instance API and uses the owning row and column context. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### cell + +[`Cell`](../../index/type-aliases/Cell.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`TValue` + +## Example + +```ts +const value = cell_getValue(cell) +``` diff --git a/docs/reference/static-functions/functions/cell_renderValue.md b/docs/reference/static-functions/functions/cell_renderValue.md new file mode 100644 index 0000000000..c7cba60c8d --- /dev/null +++ b/docs/reference/static-functions/functions/cell_renderValue.md @@ -0,0 +1,46 @@ +--- +id: cell_renderValue +title: cell_renderValue +--- + +# Function: cell\_renderValue() + +```ts +function cell_renderValue(cell): any; +``` + +Defined in: [core/cells/coreCellsFeature.utils.ts:33](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/cells/coreCellsFeature.utils.ts#L33) + +Returns value for a cell. + +This is the static implementation behind the matching cell instance API and uses the owning row and column context. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### cell + +[`Cell`](../../index/type-aliases/Cell.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`any` + +## Example + +```ts +const value = cell_renderValue(cell) +``` diff --git a/docs/reference/static-functions/functions/column_clearSorting.md b/docs/reference/static-functions/functions/column_clearSorting.md new file mode 100644 index 0000000000..9dfc86a811 --- /dev/null +++ b/docs/reference/static-functions/functions/column_clearSorting.md @@ -0,0 +1,46 @@ +--- +id: column_clearSorting +title: column_clearSorting +--- + +# Function: column\_clearSorting() + +```ts +function column_clearSorting(column): void; +``` + +Defined in: [features/row-sorting/rowSortingFeature.utils.ts:430](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L430) + +Clear Sorting. for a column. + +This is the static implementation behind the matching column instance API. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`void` + +## Example + +```ts +const value = column_clearSorting(column) +``` diff --git a/docs/reference/static-functions/functions/column_getAfter.md b/docs/reference/static-functions/functions/column_getAfter.md new file mode 100644 index 0000000000..9129631d3f --- /dev/null +++ b/docs/reference/static-functions/functions/column_getAfter.md @@ -0,0 +1,50 @@ +--- +id: column_getAfter +title: column_getAfter +--- + +# Function: column\_getAfter() + +```ts +function column_getAfter(column, position): number; +``` + +Defined in: [features/column-sizing/columnSizingFeature.utils.ts:116](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.utils.ts#L116) + +Returns after for a column. + +This derives the value from the column definition, table options, and the feature state atoms registered on the table. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +### position + +[`ColumnPinningPosition`](../../index/type-aliases/ColumnPinningPosition.md) | `"center"` + +## Returns + +`number` + +## Example + +```ts +const value = column_getAfter(column) +``` diff --git a/docs/reference/static-functions/functions/column_getAggregationFn.md b/docs/reference/static-functions/functions/column_getAggregationFn.md new file mode 100644 index 0000000000..3f10350baf --- /dev/null +++ b/docs/reference/static-functions/functions/column_getAggregationFn.md @@ -0,0 +1,49 @@ +--- +id: column_getAggregationFn +title: column_getAggregationFn +--- + +# Function: column\_getAggregationFn() + +```ts +function column_getAggregationFn(column): + | AggregationFn + | undefined; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:177](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L177) + +Returns aggregation fn for a column. + +This derives the value from the column definition, table options, and the feature state atoms registered on the table. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + + \| [`AggregationFn`](../../index/type-aliases/AggregationFn.md)\<`TFeatures`, `TData`\> + \| `undefined` + +## Example + +```ts +const value = column_getAggregationFn(column) +``` diff --git a/docs/reference/static-functions/functions/column_getAutoAggregationFn.md b/docs/reference/static-functions/functions/column_getAutoAggregationFn.md new file mode 100644 index 0000000000..d9fc69b575 --- /dev/null +++ b/docs/reference/static-functions/functions/column_getAutoAggregationFn.md @@ -0,0 +1,49 @@ +--- +id: column_getAutoAggregationFn +title: column_getAutoAggregationFn +--- + +# Function: column\_getAutoAggregationFn() + +```ts +function column_getAutoAggregationFn(column): + | AggregationFn + | undefined; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:145](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L145) + +Infers aggregation fn for a column. + +The inference uses the column definition, table options, and sampled row values when needed. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + + \| [`AggregationFn`](../../index/type-aliases/AggregationFn.md)\<`TFeatures`, `TData`\> + \| `undefined` + +## Example + +```ts +const value = column_getAutoAggregationFn(column) +``` diff --git a/docs/reference/static-functions/functions/column_getAutoFilterFn.md b/docs/reference/static-functions/functions/column_getAutoFilterFn.md new file mode 100644 index 0000000000..6242857b75 --- /dev/null +++ b/docs/reference/static-functions/functions/column_getAutoFilterFn.md @@ -0,0 +1,49 @@ +--- +id: column_getAutoFilterFn +title: column_getAutoFilterFn +--- + +# Function: column\_getAutoFilterFn() + +```ts +function column_getAutoFilterFn(column): + | FilterFn + | undefined; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:35](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L35) + +Infers filter fn for a column. + +The inference uses the column definition, table options, and sampled row values when needed. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + + \| [`FilterFn`](../../index/interfaces/FilterFn.md)\<`TFeatures`, `TData`\> + \| `undefined` + +## Example + +```ts +const value = column_getAutoFilterFn(column) +``` diff --git a/docs/reference/static-functions/functions/column_getAutoSortDir.md b/docs/reference/static-functions/functions/column_getAutoSortDir.md new file mode 100644 index 0000000000..b670003b73 --- /dev/null +++ b/docs/reference/static-functions/functions/column_getAutoSortDir.md @@ -0,0 +1,46 @@ +--- +id: column_getAutoSortDir +title: column_getAutoSortDir +--- + +# Function: column\_getAutoSortDir() + +```ts +function column_getAutoSortDir(column): "asc" | "desc"; +``` + +Defined in: [features/row-sorting/rowSortingFeature.utils.ts:126](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L126) + +Infers sort dir for a column. + +The inference uses the column definition, table options, and sampled row values when needed. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`"asc"` \| `"desc"` + +## Example + +```ts +const value = column_getAutoSortDir(column) +``` diff --git a/docs/reference/static-functions/functions/column_getAutoSortFn.md b/docs/reference/static-functions/functions/column_getAutoSortFn.md new file mode 100644 index 0000000000..42588569db --- /dev/null +++ b/docs/reference/static-functions/functions/column_getAutoSortFn.md @@ -0,0 +1,46 @@ +--- +id: column_getAutoSortFn +title: column_getAutoSortFn +--- + +# Function: column\_getAutoSortFn() + +```ts +function column_getAutoSortFn(column): SortFn; +``` + +Defined in: [features/row-sorting/rowSortingFeature.utils.ts:79](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L79) + +Infers sort fn for a column. + +The inference uses the column definition, table options, and sampled row values when needed. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +[`SortFn`](../../index/interfaces/SortFn.md)\<`TFeatures`, `TData`\> + +## Example + +```ts +const value = column_getAutoSortFn(column) +``` diff --git a/docs/reference/static-functions/functions/column_getCanFilter.md b/docs/reference/static-functions/functions/column_getCanFilter.md new file mode 100644 index 0000000000..c90c200ef2 --- /dev/null +++ b/docs/reference/static-functions/functions/column_getCanFilter.md @@ -0,0 +1,46 @@ +--- +id: column_getCanFilter +title: column_getCanFilter +--- + +# Function: column\_getCanFilter() + +```ts +function column_getCanFilter(column): boolean; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:115](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L115) + +Returns whether a column can use filter. + +This combines column options, table options, and any required accessor or feature state for the capability. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = column_getCanFilter(column) +``` diff --git a/docs/reference/static-functions/functions/column_getCanGlobalFilter.md b/docs/reference/static-functions/functions/column_getCanGlobalFilter.md new file mode 100644 index 0000000000..6a3767744b --- /dev/null +++ b/docs/reference/static-functions/functions/column_getCanGlobalFilter.md @@ -0,0 +1,46 @@ +--- +id: column_getCanGlobalFilter +title: column_getCanGlobalFilter +--- + +# Function: column\_getCanGlobalFilter() + +```ts +function column_getCanGlobalFilter(column): boolean; +``` + +Defined in: [features/global-filtering/globalFilteringFeature.utils.ts:19](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.utils.ts#L19) + +Returns whether a column can use global filter. + +This combines column options, table options, and any required accessor or feature state for the capability. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = column_getCanGlobalFilter(column) +``` diff --git a/docs/reference/static-functions/functions/column_getCanGroup.md b/docs/reference/static-functions/functions/column_getCanGroup.md new file mode 100644 index 0000000000..eb3c339482 --- /dev/null +++ b/docs/reference/static-functions/functions/column_getCanGroup.md @@ -0,0 +1,46 @@ +--- +id: column_getCanGroup +title: column_getCanGroup +--- + +# Function: column\_getCanGroup() + +```ts +function column_getCanGroup(column): boolean; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:64](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L64) + +Returns whether a column can use group. + +This combines column options, table options, and any required accessor or feature state for the capability. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = column_getCanGroup(column) +``` diff --git a/docs/reference/static-functions/functions/column_getCanHide.md b/docs/reference/static-functions/functions/column_getCanHide.md new file mode 100644 index 0000000000..43ceddf57c --- /dev/null +++ b/docs/reference/static-functions/functions/column_getCanHide.md @@ -0,0 +1,46 @@ +--- +id: column_getCanHide +title: column_getCanHide +--- + +# Function: column\_getCanHide() + +```ts +function column_getCanHide(column): boolean; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.utils.ts:84](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.utils.ts#L84) + +Returns whether a column can use hide. + +This combines column options, table options, and any required accessor or feature state for the capability. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = column_getCanHide(column) +``` diff --git a/docs/reference/static-functions/functions/column_getCanMultiSort.md b/docs/reference/static-functions/functions/column_getCanMultiSort.md new file mode 100644 index 0000000000..8a0a5ba23f --- /dev/null +++ b/docs/reference/static-functions/functions/column_getCanMultiSort.md @@ -0,0 +1,46 @@ +--- +id: column_getCanMultiSort +title: column_getCanMultiSort +--- + +# Function: column\_getCanMultiSort() + +```ts +function column_getCanMultiSort(column): boolean; +``` + +Defined in: [features/row-sorting/rowSortingFeature.utils.ts:366](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L366) + +Returns whether a column can use multi sort. + +This combines column options, table options, and any required accessor or feature state for the capability. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = column_getCanMultiSort(column) +``` diff --git a/docs/reference/static-functions/functions/column_getCanPin.md b/docs/reference/static-functions/functions/column_getCanPin.md new file mode 100644 index 0000000000..f81b8f871f --- /dev/null +++ b/docs/reference/static-functions/functions/column_getCanPin.md @@ -0,0 +1,46 @@ +--- +id: column_getCanPin +title: column_getCanPin +--- + +# Function: column\_getCanPin() + +```ts +function column_getCanPin(column): boolean; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:99](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L99) + +Returns whether a column can use pin. + +This combines column options, table options, and any required accessor or feature state for the capability. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = column_getCanPin(column) +``` diff --git a/docs/reference/static-functions/functions/column_getCanResize.md b/docs/reference/static-functions/functions/column_getCanResize.md new file mode 100644 index 0000000000..d995cf485f --- /dev/null +++ b/docs/reference/static-functions/functions/column_getCanResize.md @@ -0,0 +1,46 @@ +--- +id: column_getCanResize +title: column_getCanResize +--- + +# Function: column\_getCanResize() + +```ts +function column_getCanResize(column): boolean; +``` + +Defined in: [features/column-resizing/columnResizingFeature.utils.ts:47](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.utils.ts#L47) + +Returns whether a column can use resize. + +This combines column options, table options, and any required accessor or feature state for the capability. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = column_getCanResize(column) +``` diff --git a/docs/reference/static-functions/functions/column_getCanSort.md b/docs/reference/static-functions/functions/column_getCanSort.md new file mode 100644 index 0000000000..eea152bf0d --- /dev/null +++ b/docs/reference/static-functions/functions/column_getCanSort.md @@ -0,0 +1,46 @@ +--- +id: column_getCanSort +title: column_getCanSort +--- + +# Function: column\_getCanSort() + +```ts +function column_getCanSort(column): boolean; +``` + +Defined in: [features/row-sorting/rowSortingFeature.utils.ts:344](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L344) + +Returns whether a column can use sort. + +This combines column options, table options, and any required accessor or feature state for the capability. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = column_getCanSort(column) +``` diff --git a/docs/reference/static-functions/functions/column_getFacetedMinMaxValues.md b/docs/reference/static-functions/functions/column_getFacetedMinMaxValues.md new file mode 100644 index 0000000000..75478ba868 --- /dev/null +++ b/docs/reference/static-functions/functions/column_getFacetedMinMaxValues.md @@ -0,0 +1,50 @@ +--- +id: column_getFacetedMinMaxValues +title: column_getFacetedMinMaxValues +--- + +# Function: column\_getFacetedMinMaxValues() + +```ts +function column_getFacetedMinMaxValues(column, table): [number, number] | undefined; +``` + +Defined in: [features/column-faceting/columnFacetingFeature.utils.ts:17](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.utils.ts#L17) + +Returns faceted min max values for a column. + +This derives the value from the column definition, table options, and the feature state atoms registered on the table. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +\[`number`, `number`\] \| `undefined` + +## Example + +```ts +const value = column_getFacetedMinMaxValues(column) +``` diff --git a/docs/reference/static-functions/functions/column_getFacetedRowModel.md b/docs/reference/static-functions/functions/column_getFacetedRowModel.md new file mode 100644 index 0000000000..9daf7126d7 --- /dev/null +++ b/docs/reference/static-functions/functions/column_getFacetedRowModel.md @@ -0,0 +1,50 @@ +--- +id: column_getFacetedRowModel +title: column_getFacetedRowModel +--- + +# Function: column\_getFacetedRowModel() + +```ts +function column_getFacetedRowModel(column, table): RowModel; +``` + +Defined in: [features/column-faceting/columnFacetingFeature.utils.ts:41](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.utils.ts#L41) + +Returns faceted row model for a column. + +This derives the value from the column definition, table options, and the feature state atoms registered on the table. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> | `undefined` + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`RowModel`](../../index/interfaces/RowModel.md)\<`TFeatures`, `TData`\> + +## Example + +```ts +const value = column_getFacetedRowModel(column) +``` diff --git a/docs/reference/static-functions/functions/column_getFacetedUniqueValues.md b/docs/reference/static-functions/functions/column_getFacetedUniqueValues.md new file mode 100644 index 0000000000..a22728abca --- /dev/null +++ b/docs/reference/static-functions/functions/column_getFacetedUniqueValues.md @@ -0,0 +1,50 @@ +--- +id: column_getFacetedUniqueValues +title: column_getFacetedUniqueValues +--- + +# Function: column\_getFacetedUniqueValues() + +```ts +function column_getFacetedUniqueValues(column, table): Map; +``` + +Defined in: [features/column-faceting/columnFacetingFeature.utils.ts:65](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.utils.ts#L65) + +Returns faceted unique values for a column. + +This derives the value from the column definition, table options, and the feature state atoms registered on the table. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`Map`\<`any`, `number`\> + +## Example + +```ts +const value = column_getFacetedUniqueValues(column) +``` diff --git a/docs/reference/static-functions/functions/column_getFilterFn.md b/docs/reference/static-functions/functions/column_getFilterFn.md new file mode 100644 index 0000000000..dbef9bdcbc --- /dev/null +++ b/docs/reference/static-functions/functions/column_getFilterFn.md @@ -0,0 +1,49 @@ +--- +id: column_getFilterFn +title: column_getFilterFn +--- + +# Function: column\_getFilterFn() + +```ts +function column_getFilterFn(column): + | FilterFn + | undefined; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:80](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L80) + +Returns filter fn for a column. + +This derives the value from the column definition, table options, and the feature state atoms registered on the table. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + + \| [`FilterFn`](../../index/interfaces/FilterFn.md)\<`TFeatures`, `TData`\> + \| `undefined` + +## Example + +```ts +const value = column_getFilterFn(column) +``` diff --git a/docs/reference/static-functions/functions/column_getFilterIndex.md b/docs/reference/static-functions/functions/column_getFilterIndex.md new file mode 100644 index 0000000000..030c2bb3dd --- /dev/null +++ b/docs/reference/static-functions/functions/column_getFilterIndex.md @@ -0,0 +1,46 @@ +--- +id: column_getFilterIndex +title: column_getFilterIndex +--- + +# Function: column\_getFilterIndex() + +```ts +function column_getFilterIndex(column): number; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:176](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L176) + +Returns filter index for a column. + +This derives the value from the column definition, table options, and the feature state atoms registered on the table. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`number` + +## Example + +```ts +const value = column_getFilterIndex(column) +``` diff --git a/docs/reference/static-functions/functions/column_getFilterValue.md b/docs/reference/static-functions/functions/column_getFilterValue.md new file mode 100644 index 0000000000..ca5b0709d6 --- /dev/null +++ b/docs/reference/static-functions/functions/column_getFilterValue.md @@ -0,0 +1,46 @@ +--- +id: column_getFilterValue +title: column_getFilterValue +--- + +# Function: column\_getFilterValue() + +```ts +function column_getFilterValue(column): unknown; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:156](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L156) + +Returns filter value for a column. + +This derives the value from the column definition, table options, and the feature state atoms registered on the table. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`unknown` + +## Example + +```ts +const value = column_getFilterValue(column) +``` diff --git a/docs/reference/static-functions/functions/column_getFirstSortDir.md b/docs/reference/static-functions/functions/column_getFirstSortDir.md new file mode 100644 index 0000000000..e7b0e54b41 --- /dev/null +++ b/docs/reference/static-functions/functions/column_getFirstSortDir.md @@ -0,0 +1,46 @@ +--- +id: column_getFirstSortDir +title: column_getFirstSortDir +--- + +# Function: column\_getFirstSortDir() + +```ts +function column_getFirstSortDir(column): "asc" | "desc"; +``` + +Defined in: [features/row-sorting/rowSortingFeature.utils.ts:290](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L290) + +Returns first sort dir for a column. + +This derives the value from the column definition, table options, and the feature state atoms registered on the table. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`"asc"` \| `"desc"` + +## Example + +```ts +const value = column_getFirstSortDir(column) +``` diff --git a/docs/reference/static-functions/functions/column_getFlatColumns.md b/docs/reference/static-functions/functions/column_getFlatColumns.md new file mode 100644 index 0000000000..0227c82195 --- /dev/null +++ b/docs/reference/static-functions/functions/column_getFlatColumns.md @@ -0,0 +1,46 @@ +--- +id: column_getFlatColumns +title: column_getFlatColumns +--- + +# Function: column\_getFlatColumns() + +```ts +function column_getFlatColumns(column): Column[]; +``` + +Defined in: [core/columns/coreColumnsFeature.utils.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts#L24) + +Returns flat columns for a column. + +This derives the value from the column definition, table options, and the feature state atoms registered on the table. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column`](../../index/type-aliases/Column.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +[`Column`](../../index/type-aliases/Column.md)\<`TFeatures`, `TData`, `TValue`\>[] + +## Example + +```ts +const value = column_getFlatColumns(column) +``` diff --git a/docs/reference/static-functions/functions/column_getGroupedIndex.md b/docs/reference/static-functions/functions/column_getGroupedIndex.md new file mode 100644 index 0000000000..0de672dcf4 --- /dev/null +++ b/docs/reference/static-functions/functions/column_getGroupedIndex.md @@ -0,0 +1,46 @@ +--- +id: column_getGroupedIndex +title: column_getGroupedIndex +--- + +# Function: column\_getGroupedIndex() + +```ts +function column_getGroupedIndex(column): number; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:104](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L104) + +Returns grouped index for a column. + +This derives the value from the column definition, table options, and the feature state atoms registered on the table. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`number` + +## Example + +```ts +const value = column_getGroupedIndex(column) +``` diff --git a/docs/reference/static-functions/functions/column_getIndex.md b/docs/reference/static-functions/functions/column_getIndex.md new file mode 100644 index 0000000000..33c4fdaa2f --- /dev/null +++ b/docs/reference/static-functions/functions/column_getIndex.md @@ -0,0 +1,50 @@ +--- +id: column_getIndex +title: column_getIndex +--- + +# Function: column\_getIndex() + +```ts +function column_getIndex(column, position?): number; +``` + +Defined in: [features/column-ordering/columnOrderingFeature.utils.ts:35](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-ordering/columnOrderingFeature.utils.ts#L35) + +Returns index for a column. + +This derives the value from the column definition, table options, and the feature state atoms registered on the table. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +### position? + +[`ColumnPinningPosition`](../../index/type-aliases/ColumnPinningPosition.md) | `"center"` + +## Returns + +`number` + +## Example + +```ts +const value = column_getIndex(column) +``` diff --git a/docs/reference/static-functions/functions/column_getIsFiltered.md b/docs/reference/static-functions/functions/column_getIsFiltered.md new file mode 100644 index 0000000000..a877598be9 --- /dev/null +++ b/docs/reference/static-functions/functions/column_getIsFiltered.md @@ -0,0 +1,46 @@ +--- +id: column_getIsFiltered +title: column_getIsFiltered +--- + +# Function: column\_getIsFiltered() + +```ts +function column_getIsFiltered(column): boolean; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:138](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L138) + +Returns is filtered for a column. + +This derives the value from the column definition, table options, and the feature state atoms registered on the table. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = column_getIsFiltered(column) +``` diff --git a/docs/reference/static-functions/functions/column_getIsFirstColumn.md b/docs/reference/static-functions/functions/column_getIsFirstColumn.md new file mode 100644 index 0000000000..34d57c155b --- /dev/null +++ b/docs/reference/static-functions/functions/column_getIsFirstColumn.md @@ -0,0 +1,50 @@ +--- +id: column_getIsFirstColumn +title: column_getIsFirstColumn +--- + +# Function: column\_getIsFirstColumn() + +```ts +function column_getIsFirstColumn(column, position?): boolean; +``` + +Defined in: [features/column-ordering/columnOrderingFeature.utils.ts:57](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-ordering/columnOrderingFeature.utils.ts#L57) + +Returns is first column for a column. + +This derives the value from the column definition, table options, and the feature state atoms registered on the table. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +### position? + +[`ColumnPinningPosition`](../../index/type-aliases/ColumnPinningPosition.md) | `"center"` + +## Returns + +`boolean` + +## Example + +```ts +const value = column_getIsFirstColumn(column) +``` diff --git a/docs/reference/static-functions/functions/column_getIsGrouped.md b/docs/reference/static-functions/functions/column_getIsGrouped.md new file mode 100644 index 0000000000..5198dcd637 --- /dev/null +++ b/docs/reference/static-functions/functions/column_getIsGrouped.md @@ -0,0 +1,46 @@ +--- +id: column_getIsGrouped +title: column_getIsGrouped +--- + +# Function: column\_getIsGrouped() + +```ts +function column_getIsGrouped(column): boolean; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:86](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L86) + +Returns is grouped for a column. + +This derives the value from the column definition, table options, and the feature state atoms registered on the table. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = column_getIsGrouped(column) +``` diff --git a/docs/reference/static-functions/functions/column_getIsLastColumn.md b/docs/reference/static-functions/functions/column_getIsLastColumn.md new file mode 100644 index 0000000000..7c19873d9f --- /dev/null +++ b/docs/reference/static-functions/functions/column_getIsLastColumn.md @@ -0,0 +1,50 @@ +--- +id: column_getIsLastColumn +title: column_getIsLastColumn +--- + +# Function: column\_getIsLastColumn() + +```ts +function column_getIsLastColumn(column, position?): boolean; +``` + +Defined in: [features/column-ordering/columnOrderingFeature.utils.ts:79](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-ordering/columnOrderingFeature.utils.ts#L79) + +Returns is last column for a column. + +This derives the value from the column definition, table options, and the feature state atoms registered on the table. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +### position? + +[`ColumnPinningPosition`](../../index/type-aliases/ColumnPinningPosition.md) | `"center"` + +## Returns + +`boolean` + +## Example + +```ts +const value = column_getIsLastColumn(column) +``` diff --git a/docs/reference/static-functions/functions/column_getIsPinned.md b/docs/reference/static-functions/functions/column_getIsPinned.md new file mode 100644 index 0000000000..3d2a42aa21 --- /dev/null +++ b/docs/reference/static-functions/functions/column_getIsPinned.md @@ -0,0 +1,46 @@ +--- +id: column_getIsPinned +title: column_getIsPinned +--- + +# Function: column\_getIsPinned() + +```ts +function column_getIsPinned(column): ColumnPinningPosition; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:125](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L125) + +Returns is pinned for a column. + +This derives the value from the column definition, table options, and the feature state atoms registered on the table. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +[`ColumnPinningPosition`](../../index/type-aliases/ColumnPinningPosition.md) + +## Example + +```ts +const value = column_getIsPinned(column) +``` diff --git a/docs/reference/static-functions/functions/column_getIsResizing.md b/docs/reference/static-functions/functions/column_getIsResizing.md new file mode 100644 index 0000000000..6eee398d5f --- /dev/null +++ b/docs/reference/static-functions/functions/column_getIsResizing.md @@ -0,0 +1,46 @@ +--- +id: column_getIsResizing +title: column_getIsResizing +--- + +# Function: column\_getIsResizing() + +```ts +function column_getIsResizing(column): boolean; +``` + +Defined in: [features/column-resizing/columnResizingFeature.utils.ts:68](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.utils.ts#L68) + +Returns is resizing for a column. + +This derives the value from the column definition, table options, and the feature state atoms registered on the table. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = column_getIsResizing(column) +``` diff --git a/docs/reference/static-functions/functions/column_getIsSorted.md b/docs/reference/static-functions/functions/column_getIsSorted.md new file mode 100644 index 0000000000..fb8dfcd7c2 --- /dev/null +++ b/docs/reference/static-functions/functions/column_getIsSorted.md @@ -0,0 +1,46 @@ +--- +id: column_getIsSorted +title: column_getIsSorted +--- + +# Function: column\_getIsSorted() + +```ts +function column_getIsSorted(column): false | SortDirection; +``` + +Defined in: [features/row-sorting/rowSortingFeature.utils.ts:388](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L388) + +Returns is sorted for a column. + +This derives the value from the column definition, table options, and the feature state atoms registered on the table. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`false` \| [`SortDirection`](../../index/type-aliases/SortDirection.md) + +## Example + +```ts +const value = column_getIsSorted(column) +``` diff --git a/docs/reference/static-functions/functions/column_getIsVisible.md b/docs/reference/static-functions/functions/column_getIsVisible.md new file mode 100644 index 0000000000..38d01b3e7f --- /dev/null +++ b/docs/reference/static-functions/functions/column_getIsVisible.md @@ -0,0 +1,46 @@ +--- +id: column_getIsVisible +title: column_getIsVisible +--- + +# Function: column\_getIsVisible() + +```ts +function column_getIsVisible(column): boolean; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.utils.ts:59](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.utils.ts#L59) + +Returns is visible for a column. + +This derives the value from the column definition, table options, and the feature state atoms registered on the table. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = column_getIsVisible(column) +``` diff --git a/docs/reference/static-functions/functions/column_getLeafColumns.md b/docs/reference/static-functions/functions/column_getLeafColumns.md new file mode 100644 index 0000000000..8ccc0aa63e --- /dev/null +++ b/docs/reference/static-functions/functions/column_getLeafColumns.md @@ -0,0 +1,46 @@ +--- +id: column_getLeafColumns +title: column_getLeafColumns +--- + +# Function: column\_getLeafColumns() + +```ts +function column_getLeafColumns(column): Column[]; +``` + +Defined in: [core/columns/coreColumnsFeature.utils.ts:44](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts#L44) + +Returns leaf columns for a column. + +This derives the value from the column definition, table options, and the feature state atoms registered on the table. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column`](../../index/type-aliases/Column.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +[`Column`](../../index/type-aliases/Column.md)\<`TFeatures`, `TData`, `TValue`\>[] + +## Example + +```ts +const value = column_getLeafColumns(column) +``` diff --git a/docs/reference/static-functions/functions/column_getNextSortingOrder.md b/docs/reference/static-functions/functions/column_getNextSortingOrder.md new file mode 100644 index 0000000000..916a878dc8 --- /dev/null +++ b/docs/reference/static-functions/functions/column_getNextSortingOrder.md @@ -0,0 +1,50 @@ +--- +id: column_getNextSortingOrder +title: column_getNextSortingOrder +--- + +# Function: column\_getNextSortingOrder() + +```ts +function column_getNextSortingOrder(column, multi?): false | "asc" | "desc"; +``` + +Defined in: [features/row-sorting/rowSortingFeature.utils.ts:312](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L312) + +Returns next sorting order for a column. + +This derives the value from the column definition, table options, and the feature state atoms registered on the table. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +### multi? + +`boolean` + +## Returns + +`false` \| `"asc"` \| `"desc"` + +## Example + +```ts +const value = column_getNextSortingOrder(column) +``` diff --git a/docs/reference/static-functions/functions/column_getPinnedIndex.md b/docs/reference/static-functions/functions/column_getPinnedIndex.md new file mode 100644 index 0000000000..dd68a88bc5 --- /dev/null +++ b/docs/reference/static-functions/functions/column_getPinnedIndex.md @@ -0,0 +1,46 @@ +--- +id: column_getPinnedIndex +title: column_getPinnedIndex +--- + +# Function: column\_getPinnedIndex() + +```ts +function column_getPinnedIndex(column): number; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:153](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L153) + +Returns pinned index for a column. + +This derives the value from the column definition, table options, and the feature state atoms registered on the table. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`number` + +## Example + +```ts +const value = column_getPinnedIndex(column) +``` diff --git a/docs/reference/static-functions/functions/column_getSize.md b/docs/reference/static-functions/functions/column_getSize.md new file mode 100644 index 0000000000..9af723efbf --- /dev/null +++ b/docs/reference/static-functions/functions/column_getSize.md @@ -0,0 +1,46 @@ +--- +id: column_getSize +title: column_getSize +--- + +# Function: column\_getSize() + +```ts +function column_getSize(column): number; +``` + +Defined in: [features/column-sizing/columnSizingFeature.utils.ts:59](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.utils.ts#L59) + +Returns size for a column. + +This derives the value from the column definition, table options, and the feature state atoms registered on the table. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`number` + +## Example + +```ts +const value = column_getSize(column) +``` diff --git a/docs/reference/static-functions/functions/column_getSortFn.md b/docs/reference/static-functions/functions/column_getSortFn.md new file mode 100644 index 0000000000..274512d633 --- /dev/null +++ b/docs/reference/static-functions/functions/column_getSortFn.md @@ -0,0 +1,46 @@ +--- +id: column_getSortFn +title: column_getSortFn +--- + +# Function: column\_getSortFn() + +```ts +function column_getSortFn(column): SortFn; +``` + +Defined in: [features/row-sorting/rowSortingFeature.utils.ts:152](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L152) + +Returns sort fn for a column. + +This derives the value from the column definition, table options, and the feature state atoms registered on the table. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +[`SortFn`](../../index/interfaces/SortFn.md)\<`TFeatures`, `TData`\> + +## Example + +```ts +const value = column_getSortFn(column) +``` diff --git a/docs/reference/static-functions/functions/column_getSortIndex.md b/docs/reference/static-functions/functions/column_getSortIndex.md new file mode 100644 index 0000000000..2d7538763e --- /dev/null +++ b/docs/reference/static-functions/functions/column_getSortIndex.md @@ -0,0 +1,46 @@ +--- +id: column_getSortIndex +title: column_getSortIndex +--- + +# Function: column\_getSortIndex() + +```ts +function column_getSortIndex(column): number; +``` + +Defined in: [features/row-sorting/rowSortingFeature.utils.ts:409](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L409) + +Returns sort index for a column. + +This derives the value from the column definition, table options, and the feature state atoms registered on the table. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`number` + +## Example + +```ts +const value = column_getSortIndex(column) +``` diff --git a/docs/reference/static-functions/functions/column_getStart.md b/docs/reference/static-functions/functions/column_getStart.md new file mode 100644 index 0000000000..54a83ed99d --- /dev/null +++ b/docs/reference/static-functions/functions/column_getStart.md @@ -0,0 +1,50 @@ +--- +id: column_getStart +title: column_getStart +--- + +# Function: column\_getStart() + +```ts +function column_getStart(column, position): number; +``` + +Defined in: [features/column-sizing/columnSizingFeature.utils.ts:86](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.utils.ts#L86) + +Returns start for a column. + +This derives the value from the column definition, table options, and the feature state atoms registered on the table. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +### position + +[`ColumnPinningPosition`](../../index/type-aliases/ColumnPinningPosition.md) | `"center"` + +## Returns + +`number` + +## Example + +```ts +const value = column_getStart(column) +``` diff --git a/docs/reference/static-functions/functions/column_getToggleGroupingHandler.md b/docs/reference/static-functions/functions/column_getToggleGroupingHandler.md new file mode 100644 index 0000000000..39cb88d907 --- /dev/null +++ b/docs/reference/static-functions/functions/column_getToggleGroupingHandler.md @@ -0,0 +1,52 @@ +--- +id: column_getToggleGroupingHandler +title: column_getToggleGroupingHandler +--- + +# Function: column\_getToggleGroupingHandler() + +```ts +function column_getToggleGroupingHandler(column): () => void; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:122](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L122) + +Returns an event handler for toggling grouping handler. + +The handler is intended for direct use in column header controls such as buttons or checkboxes. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +```ts +(): void; +``` + +### Returns + +`void` + +## Example + +```ts +const value = column_getToggleGroupingHandler(column) +``` diff --git a/docs/reference/static-functions/functions/column_getToggleSortingHandler.md b/docs/reference/static-functions/functions/column_getToggleSortingHandler.md new file mode 100644 index 0000000000..fa05453a92 --- /dev/null +++ b/docs/reference/static-functions/functions/column_getToggleSortingHandler.md @@ -0,0 +1,58 @@ +--- +id: column_getToggleSortingHandler +title: column_getToggleSortingHandler +--- + +# Function: column\_getToggleSortingHandler() + +```ts +function column_getToggleSortingHandler(column): (e) => void; +``` + +Defined in: [features/row-sorting/rowSortingFeature.utils.ts:451](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L451) + +Returns an event handler for toggling sorting handler. + +The handler is intended for direct use in column header controls such as buttons or checkboxes. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +```ts +(e): void; +``` + +### Parameters + +#### e + +`unknown` + +### Returns + +`void` + +## Example + +```ts +const value = column_getToggleSortingHandler(column) +``` diff --git a/docs/reference/static-functions/functions/column_getToggleVisibilityHandler.md b/docs/reference/static-functions/functions/column_getToggleVisibilityHandler.md new file mode 100644 index 0000000000..272a93a36c --- /dev/null +++ b/docs/reference/static-functions/functions/column_getToggleVisibilityHandler.md @@ -0,0 +1,58 @@ +--- +id: column_getToggleVisibilityHandler +title: column_getToggleVisibilityHandler +--- + +# Function: column\_getToggleVisibilityHandler() + +```ts +function column_getToggleVisibilityHandler(column): (e) => void; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.utils.ts:105](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.utils.ts#L105) + +Returns an event handler for toggling visibility handler. + +The handler is intended for direct use in column header controls such as buttons or checkboxes. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +```ts +(e): void; +``` + +### Parameters + +#### e + +`unknown` + +### Returns + +`void` + +## Example + +```ts +const value = column_getToggleVisibilityHandler(column) +``` diff --git a/docs/reference/static-functions/functions/column_pin.md b/docs/reference/static-functions/functions/column_pin.md new file mode 100644 index 0000000000..c731ae73e1 --- /dev/null +++ b/docs/reference/static-functions/functions/column_pin.md @@ -0,0 +1,50 @@ +--- +id: column_pin +title: column_pin +--- + +# Function: column\_pin() + +```ts +function column_pin(column, position): void; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:51](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L51) + +Pin. for a column. + +This is the static implementation behind the matching column instance API. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +### position + +[`ColumnPinningPosition`](../../index/type-aliases/ColumnPinningPosition.md) + +## Returns + +`void` + +## Example + +```ts +column_pin(column, 'left') +``` diff --git a/docs/reference/static-functions/functions/column_resetSize.md b/docs/reference/static-functions/functions/column_resetSize.md new file mode 100644 index 0000000000..c5aff8f3be --- /dev/null +++ b/docs/reference/static-functions/functions/column_resetSize.md @@ -0,0 +1,46 @@ +--- +id: column_resetSize +title: column_resetSize +--- + +# Function: column\_resetSize() + +```ts +function column_resetSize(column): void; +``` + +Defined in: [features/column-sizing/columnSizingFeature.utils.ts:148](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.utils.ts#L148) + +Reset Size. for a column. + +This is the static implementation behind the matching column instance API. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`void` + +## Example + +```ts +const value = column_resetSize(column) +``` diff --git a/docs/reference/static-functions/functions/column_setFilterValue.md b/docs/reference/static-functions/functions/column_setFilterValue.md new file mode 100644 index 0000000000..11523d23bf --- /dev/null +++ b/docs/reference/static-functions/functions/column_setFilterValue.md @@ -0,0 +1,50 @@ +--- +id: column_setFilterValue +title: column_setFilterValue +--- + +# Function: column\_setFilterValue() + +```ts +function column_setFilterValue(column, value): void; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:198](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L198) + +Updates filter value for a column. + +This delegates to the owning table state updater so external state, external atoms, and internal state stay synchronized. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +### value + +`any` + +## Returns + +`void` + +## Example + +```ts +column_setFilterValue(column, (old) => old) +``` diff --git a/docs/reference/static-functions/functions/column_toggleGrouping.md b/docs/reference/static-functions/functions/column_toggleGrouping.md new file mode 100644 index 0000000000..066bd9ca81 --- /dev/null +++ b/docs/reference/static-functions/functions/column_toggleGrouping.md @@ -0,0 +1,46 @@ +--- +id: column_toggleGrouping +title: column_toggleGrouping +--- + +# Function: column\_toggleGrouping() + +```ts +function column_toggleGrouping(column): void; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:39](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L39) + +Toggles grouping for a column. + +The update is applied through the owning table state slice and respects the feature options for that column. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`void` + +## Example + +```ts +column_toggleGrouping(column) +``` diff --git a/docs/reference/static-functions/functions/column_toggleSorting.md b/docs/reference/static-functions/functions/column_toggleSorting.md new file mode 100644 index 0000000000..c8a1cfb7b9 --- /dev/null +++ b/docs/reference/static-functions/functions/column_toggleSorting.md @@ -0,0 +1,57 @@ +--- +id: column_toggleSorting +title: column_toggleSorting +--- + +# Function: column\_toggleSorting() + +```ts +function column_toggleSorting( + column, + desc?, + multi?): void; +``` + +Defined in: [features/row-sorting/rowSortingFeature.utils.ts:177](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L177) + +Toggles sorting for a column. + +The update is applied through the owning table state slice and respects the feature options for that column. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +### desc? + +`boolean` + +### multi? + +`boolean` + +## Returns + +`void` + +## Example + +```ts +column_toggleSorting(column) +``` diff --git a/docs/reference/static-functions/functions/column_toggleVisibility.md b/docs/reference/static-functions/functions/column_toggleVisibility.md new file mode 100644 index 0000000000..f94511b886 --- /dev/null +++ b/docs/reference/static-functions/functions/column_toggleVisibility.md @@ -0,0 +1,50 @@ +--- +id: column_toggleVisibility +title: column_toggleVisibility +--- + +# Function: column\_toggleVisibility() + +```ts +function column_toggleVisibility(column, visible?): void; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.utils.ts:34](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.utils.ts#L34) + +Toggles visibility for a column. + +The update is applied through the owning table state slice and respects the feature options for that column. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### column + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +### visible? + +`boolean` + +## Returns + +`void` + +## Example + +```ts +column_toggleVisibility(column) +``` diff --git a/docs/reference/static-functions/functions/getDefaultColumnFiltersState.md b/docs/reference/static-functions/functions/getDefaultColumnFiltersState.md new file mode 100644 index 0000000000..cebd7afcbc --- /dev/null +++ b/docs/reference/static-functions/functions/getDefaultColumnFiltersState.md @@ -0,0 +1,26 @@ +--- +id: getDefaultColumnFiltersState +title: getDefaultColumnFiltersState +--- + +# Function: getDefaultColumnFiltersState() + +```ts +function getDefaultColumnFiltersState(): ColumnFiltersState; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:21](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L21) + +Returns the default column filters state. + +Feature constructors use this value to initialize the table state or option defaults when no user value is provided. + +## Returns + +[`ColumnFiltersState`](../../index/type-aliases/ColumnFiltersState.md) + +## Example + +```ts +const initialValue = getDefaultColumnFiltersState() +``` diff --git a/docs/reference/static-functions/functions/getDefaultColumnOrderState.md b/docs/reference/static-functions/functions/getDefaultColumnOrderState.md new file mode 100644 index 0000000000..75d0ed4c27 --- /dev/null +++ b/docs/reference/static-functions/functions/getDefaultColumnOrderState.md @@ -0,0 +1,26 @@ +--- +id: getDefaultColumnOrderState +title: getDefaultColumnOrderState +--- + +# Function: getDefaultColumnOrderState() + +```ts +function getDefaultColumnOrderState(): ColumnOrderState; +``` + +Defined in: [features/column-ordering/columnOrderingFeature.utils.ts:21](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-ordering/columnOrderingFeature.utils.ts#L21) + +Returns the default column order state. + +Feature constructors use this value to initialize the table state or option defaults when no user value is provided. + +## Returns + +[`ColumnOrderState`](../../index/type-aliases/ColumnOrderState.md) + +## Example + +```ts +const initialValue = getDefaultColumnOrderState() +``` diff --git a/docs/reference/static-functions/functions/getDefaultColumnPinningState.md b/docs/reference/static-functions/functions/getDefaultColumnPinningState.md new file mode 100644 index 0000000000..ffbcc8ebe9 --- /dev/null +++ b/docs/reference/static-functions/functions/getDefaultColumnPinningState.md @@ -0,0 +1,26 @@ +--- +id: getDefaultColumnPinningState +title: getDefaultColumnPinningState +--- + +# Function: getDefaultColumnPinningState() + +```ts +function getDefaultColumnPinningState(): ColumnPinningState; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:32](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L32) + +Returns the default column pinning state. + +Feature constructors use this value to initialize the table state or option defaults when no user value is provided. + +## Returns + +[`ColumnPinningState`](../../index/interfaces/ColumnPinningState.md) + +## Example + +```ts +const initialValue = getDefaultColumnPinningState() +``` diff --git a/docs/reference/static-functions/functions/getDefaultColumnResizingState.md b/docs/reference/static-functions/functions/getDefaultColumnResizingState.md new file mode 100644 index 0000000000..43eba02e9e --- /dev/null +++ b/docs/reference/static-functions/functions/getDefaultColumnResizingState.md @@ -0,0 +1,26 @@ +--- +id: getDefaultColumnResizingState +title: getDefaultColumnResizingState +--- + +# Function: getDefaultColumnResizingState() + +```ts +function getDefaultColumnResizingState(): columnResizingState; +``` + +Defined in: [features/column-resizing/columnResizingFeature.utils.ts:26](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.utils.ts#L26) + +Returns the default column resizing state. + +Feature constructors use this value to initialize the table state or option defaults when no user value is provided. + +## Returns + +[`columnResizingState`](../../index/interfaces/columnResizingState.md) + +## Example + +```ts +const initialValue = getDefaultColumnResizingState() +``` diff --git a/docs/reference/static-functions/functions/getDefaultColumnSizingColumnDef.md b/docs/reference/static-functions/functions/getDefaultColumnSizingColumnDef.md new file mode 100644 index 0000000000..40989e6659 --- /dev/null +++ b/docs/reference/static-functions/functions/getDefaultColumnSizingColumnDef.md @@ -0,0 +1,44 @@ +--- +id: getDefaultColumnSizingColumnDef +title: getDefaultColumnSizingColumnDef +--- + +# Function: getDefaultColumnSizingColumnDef() + +```ts +function getDefaultColumnSizingColumnDef(): object; +``` + +Defined in: [features/column-sizing/columnSizingFeature.utils.ts:41](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.utils.ts#L41) + +Returns the default column sizing column def. + +Feature constructors use this value to initialize the table state or option defaults when no user value is provided. + +## Returns + +`object` + +### maxSize + +```ts +maxSize: number = Number.MAX_SAFE_INTEGER; +``` + +### minSize + +```ts +minSize: number = 20; +``` + +### size + +```ts +size: number = 150; +``` + +## Example + +```ts +const initialValue = getDefaultColumnSizingColumnDef() +``` diff --git a/docs/reference/static-functions/functions/getDefaultColumnSizingState.md b/docs/reference/static-functions/functions/getDefaultColumnSizingState.md new file mode 100644 index 0000000000..a908364fc2 --- /dev/null +++ b/docs/reference/static-functions/functions/getDefaultColumnSizingState.md @@ -0,0 +1,26 @@ +--- +id: getDefaultColumnSizingState +title: getDefaultColumnSizingState +--- + +# Function: getDefaultColumnSizingState() + +```ts +function getDefaultColumnSizingState(): ColumnSizingState; +``` + +Defined in: [features/column-sizing/columnSizingFeature.utils.ts:27](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.utils.ts#L27) + +Returns the default column sizing state. + +Feature constructors use this value to initialize the table state or option defaults when no user value is provided. + +## Returns + +[`ColumnSizingState`](../../index/type-aliases/ColumnSizingState.md) + +## Example + +```ts +const initialValue = getDefaultColumnSizingState() +``` diff --git a/docs/reference/static-functions/functions/getDefaultColumnVisibilityState.md b/docs/reference/static-functions/functions/getDefaultColumnVisibilityState.md new file mode 100644 index 0000000000..b3326917c6 --- /dev/null +++ b/docs/reference/static-functions/functions/getDefaultColumnVisibilityState.md @@ -0,0 +1,26 @@ +--- +id: getDefaultColumnVisibilityState +title: getDefaultColumnVisibilityState +--- + +# Function: getDefaultColumnVisibilityState() + +```ts +function getDefaultColumnVisibilityState(): ColumnVisibilityState; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.utils.ts:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.utils.ts#L20) + +Returns the default column visibility state. + +Feature constructors use this value to initialize the table state or option defaults when no user value is provided. + +## Returns + +[`ColumnVisibilityState`](../../index/type-aliases/ColumnVisibilityState.md) + +## Example + +```ts +const initialValue = getDefaultColumnVisibilityState() +``` diff --git a/docs/reference/static-functions/functions/getDefaultExpandedState.md b/docs/reference/static-functions/functions/getDefaultExpandedState.md new file mode 100644 index 0000000000..bd50db92ee --- /dev/null +++ b/docs/reference/static-functions/functions/getDefaultExpandedState.md @@ -0,0 +1,26 @@ +--- +id: getDefaultExpandedState +title: getDefaultExpandedState +--- + +# Function: getDefaultExpandedState() + +```ts +function getDefaultExpandedState(): ExpandedState; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:21](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L21) + +Returns the default expanded state. + +Feature constructors use this value to initialize the table state or option defaults when no user value is provided. + +## Returns + +[`ExpandedState`](../../index/type-aliases/ExpandedState.md) + +## Example + +```ts +const initialValue = getDefaultExpandedState() +``` diff --git a/docs/reference/static-functions/functions/getDefaultGroupingState.md b/docs/reference/static-functions/functions/getDefaultGroupingState.md new file mode 100644 index 0000000000..3656efe010 --- /dev/null +++ b/docs/reference/static-functions/functions/getDefaultGroupingState.md @@ -0,0 +1,26 @@ +--- +id: getDefaultGroupingState +title: getDefaultGroupingState +--- + +# Function: getDefaultGroupingState() + +```ts +function getDefaultGroupingState(): GroupingState; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:25](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L25) + +Returns the default grouping state. + +Feature constructors use this value to initialize the table state or option defaults when no user value is provided. + +## Returns + +[`GroupingState`](../../index/type-aliases/GroupingState.md) + +## Example + +```ts +const initialValue = getDefaultGroupingState() +``` diff --git a/docs/reference/static-functions/functions/getDefaultPaginationState.md b/docs/reference/static-functions/functions/getDefaultPaginationState.md new file mode 100644 index 0000000000..684911b720 --- /dev/null +++ b/docs/reference/static-functions/functions/getDefaultPaginationState.md @@ -0,0 +1,26 @@ +--- +id: getDefaultPaginationState +title: getDefaultPaginationState +--- + +# Function: getDefaultPaginationState() + +```ts +function getDefaultPaginationState(): PaginationState; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.utils.ts:20](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.utils.ts#L20) + +Returns the default pagination state. + +Feature constructors use this value to initialize the table state or option defaults when no user value is provided. + +## Returns + +[`PaginationState`](../../index/interfaces/PaginationState.md) + +## Example + +```ts +const initialValue = getDefaultPaginationState() +``` diff --git a/docs/reference/static-functions/functions/getDefaultRowPinningState.md b/docs/reference/static-functions/functions/getDefaultRowPinningState.md new file mode 100644 index 0000000000..77455d9759 --- /dev/null +++ b/docs/reference/static-functions/functions/getDefaultRowPinningState.md @@ -0,0 +1,26 @@ +--- +id: getDefaultRowPinningState +title: getDefaultRowPinningState +--- + +# Function: getDefaultRowPinningState() + +```ts +function getDefaultRowPinningState(): RowPinningState; +``` + +Defined in: [features/row-pinning/rowPinningFeature.utils.ts:24](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.utils.ts#L24) + +Returns the default row pinning state. + +Feature constructors use this value to initialize the table state or option defaults when no user value is provided. + +## Returns + +[`RowPinningState`](../../index/interfaces/RowPinningState.md) + +## Example + +```ts +const initialValue = getDefaultRowPinningState() +``` diff --git a/docs/reference/static-functions/functions/getDefaultRowSelectionState.md b/docs/reference/static-functions/functions/getDefaultRowSelectionState.md new file mode 100644 index 0000000000..377690d3d1 --- /dev/null +++ b/docs/reference/static-functions/functions/getDefaultRowSelectionState.md @@ -0,0 +1,26 @@ +--- +id: getDefaultRowSelectionState +title: getDefaultRowSelectionState +--- + +# Function: getDefaultRowSelectionState() + +```ts +function getDefaultRowSelectionState(): RowSelectionState; +``` + +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:21](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L21) + +Returns the default row selection state. + +Feature constructors use this value to initialize the table state or option defaults when no user value is provided. + +## Returns + +[`RowSelectionState`](../../index/type-aliases/RowSelectionState.md) + +## Example + +```ts +const initialValue = getDefaultRowSelectionState() +``` diff --git a/docs/reference/static-functions/functions/getDefaultSortingState.md b/docs/reference/static-functions/functions/getDefaultSortingState.md new file mode 100644 index 0000000000..b01ac7c0b3 --- /dev/null +++ b/docs/reference/static-functions/functions/getDefaultSortingState.md @@ -0,0 +1,26 @@ +--- +id: getDefaultSortingState +title: getDefaultSortingState +--- + +# Function: getDefaultSortingState() + +```ts +function getDefaultSortingState(): SortingState; +``` + +Defined in: [features/row-sorting/rowSortingFeature.utils.ts:25](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L25) + +Returns the default sorting state. + +Feature constructors use this value to initialize the table state or option defaults when no user value is provided. + +## Returns + +[`SortingState`](../../index/type-aliases/SortingState.md) + +## Example + +```ts +const initialValue = getDefaultSortingState() +``` diff --git a/docs/reference/static-functions/functions/header_getContext.md b/docs/reference/static-functions/functions/header_getContext.md new file mode 100644 index 0000000000..84eaecda19 --- /dev/null +++ b/docs/reference/static-functions/functions/header_getContext.md @@ -0,0 +1,64 @@ +--- +id: header_getContext +title: header_getContext +--- + +# Function: header\_getContext() + +```ts +function header_getContext(header): object; +``` + +Defined in: [core/headers/coreHeadersFeature.utils.ts:56](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.utils.ts#L56) + +Returns context for a header. + +This is the static implementation behind the matching header instance API and can account for nested header groups. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` + +## Parameters + +### header + +[`Header`](../../index/type-aliases/Header.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`object` + +### column + +```ts +column: Column = header.column; +``` + +### header + +```ts +header: Header; +``` + +### table + +```ts +table: Table_Internal = header.column.table; +``` + +## Example + +```ts +const value = header_getContext(header) +``` diff --git a/docs/reference/static-functions/functions/header_getLeafHeaders.md b/docs/reference/static-functions/functions/header_getLeafHeaders.md new file mode 100644 index 0000000000..6cee35518c --- /dev/null +++ b/docs/reference/static-functions/functions/header_getLeafHeaders.md @@ -0,0 +1,46 @@ +--- +id: header_getLeafHeaders +title: header_getLeafHeaders +--- + +# Function: header\_getLeafHeaders() + +```ts +function header_getLeafHeaders(header): Header[]; +``` + +Defined in: [core/headers/coreHeadersFeature.utils.ts:27](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.utils.ts#L27) + +Returns leaf headers for a header. + +This is the static implementation behind the matching header instance API and can account for nested header groups. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` + +## Parameters + +### header + +[`Header`](../../index/type-aliases/Header.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +[`Header`](../../index/type-aliases/Header.md)\<`TFeatures`, `TData`, `TValue`\>[] + +## Example + +```ts +const value = header_getLeafHeaders(header) +``` diff --git a/docs/reference/static-functions/functions/header_getResizeHandler.md b/docs/reference/static-functions/functions/header_getResizeHandler.md new file mode 100644 index 0000000000..188dd364ce --- /dev/null +++ b/docs/reference/static-functions/functions/header_getResizeHandler.md @@ -0,0 +1,62 @@ +--- +id: header_getResizeHandler +title: header_getResizeHandler +--- + +# Function: header\_getResizeHandler() + +```ts +function header_getResizeHandler(header, _contextDocument?): (event) => void; +``` + +Defined in: [features/column-resizing/columnResizingFeature.utils.ts:88](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.utils.ts#L88) + +Returns resize handler for a header. + +This is the static implementation behind the matching header instance API and can account for nested header groups. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### header + +[`Header`](../../index/type-aliases/Header.md)\<`TFeatures`, `TData`, `TValue`\> + +### \_contextDocument? + +`Document` + +## Returns + +```ts +(event): void; +``` + +### Parameters + +#### event + +`unknown` + +### Returns + +`void` + +## Example + +```ts +const onMouseDown = header_getResizeHandler(header) +``` diff --git a/docs/reference/static-functions/functions/header_getSize.md b/docs/reference/static-functions/functions/header_getSize.md new file mode 100644 index 0000000000..5669a5386c --- /dev/null +++ b/docs/reference/static-functions/functions/header_getSize.md @@ -0,0 +1,46 @@ +--- +id: header_getSize +title: header_getSize +--- + +# Function: header\_getSize() + +```ts +function header_getSize(header): number; +``` + +Defined in: [features/column-sizing/columnSizingFeature.utils.ts:168](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.utils.ts#L168) + +Returns size for a header. + +This is the static implementation behind the matching header instance API and can account for nested header groups. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### header + +[`Header`](../../index/type-aliases/Header.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`number` + +## Example + +```ts +const value = header_getSize(header) +``` diff --git a/docs/reference/static-functions/functions/header_getStart.md b/docs/reference/static-functions/functions/header_getStart.md new file mode 100644 index 0000000000..fdb2bdc4ca --- /dev/null +++ b/docs/reference/static-functions/functions/header_getStart.md @@ -0,0 +1,46 @@ +--- +id: header_getStart +title: header_getStart +--- + +# Function: header\_getStart() + +```ts +function header_getStart(header): number; +``` + +Defined in: [features/column-sizing/columnSizingFeature.utils.ts:198](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.utils.ts#L198) + +Returns start for a header. + +This is the static implementation behind the matching header instance API and can account for nested header groups. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### header + +[`Header`](../../index/type-aliases/Header.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`number` + +## Example + +```ts +const value = header_getStart(header) +``` diff --git a/docs/reference/static-functions/functions/isRowSelected.md b/docs/reference/static-functions/functions/isRowSelected.md new file mode 100644 index 0000000000..c42a60b0a8 --- /dev/null +++ b/docs/reference/static-functions/functions/isRowSelected.md @@ -0,0 +1,40 @@ +--- +id: isRowSelected +title: isRowSelected +--- + +# Function: isRowSelected() + +```ts +function isRowSelected(row): boolean; +``` + +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:668](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L668) + +Returns whether a row id is selected in the current row selection state. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +## Returns + +`boolean` + +## Example + +```ts +const selected = isRowSelected(row, selection) +``` diff --git a/docs/reference/static-functions/functions/isSubRowSelected.md b/docs/reference/static-functions/functions/isSubRowSelected.md new file mode 100644 index 0000000000..cf3545dcc3 --- /dev/null +++ b/docs/reference/static-functions/functions/isSubRowSelected.md @@ -0,0 +1,42 @@ +--- +id: isSubRowSelected +title: isSubRowSelected +--- + +# Function: isSubRowSelected() + +```ts +function isSubRowSelected(row): boolean | "some" | "all"; +``` + +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:685](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L685) + +Returns whether all, some, or none of a row's selectable descendants are selected. + +The result is used to drive indeterminate row selection UI. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +## Returns + +`boolean` \| `"some"` \| `"all"` + +## Example + +```ts +const selectedState = isSubRowSelected(row, selection, table) +``` diff --git a/docs/reference/static-functions/functions/isTouchStartEvent.md b/docs/reference/static-functions/functions/isTouchStartEvent.md new file mode 100644 index 0000000000..603fd8dd17 --- /dev/null +++ b/docs/reference/static-functions/functions/isTouchStartEvent.md @@ -0,0 +1,32 @@ +--- +id: isTouchStartEvent +title: isTouchStartEvent +--- + +# Function: isTouchStartEvent() + +```ts +function isTouchStartEvent(e): e is TouchEvent; +``` + +Defined in: [features/column-resizing/columnResizingFeature.utils.ts:355](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.utils.ts#L355) + +Returns whether an event is a touch-start event. + +Column resizing uses this to normalize mouse and touch resize interactions. + +## Parameters + +### e + +`unknown` + +## Returns + +`e is TouchEvent` + +## Example + +```ts +const isTouch = isTouchStartEvent(event) +``` diff --git a/docs/reference/static-functions/functions/orderColumns.md b/docs/reference/static-functions/functions/orderColumns.md new file mode 100644 index 0000000000..8b8b9141e6 --- /dev/null +++ b/docs/reference/static-functions/functions/orderColumns.md @@ -0,0 +1,46 @@ +--- +id: orderColumns +title: orderColumns +--- + +# Function: orderColumns() + +```ts +function orderColumns(table, leafColumns): Column_Internal[]; +``` + +Defined in: [features/column-ordering/columnOrderingFeature.utils.ts:188](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-ordering/columnOrderingFeature.utils.ts#L188) + +Orders leaf columns with manual ordering, grouping, and pinning rules. + +This helper is used by the column ordering feature to produce the final visible column order. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### leafColumns + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `unknown`\>[] + +## Returns + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `unknown`\>[] + +## Example + +```ts +const orderedColumns = orderColumns(leafColumns, columnOrder, grouping, groupedColumnMode) +``` diff --git a/docs/reference/static-functions/functions/passiveEventSupported.md b/docs/reference/static-functions/functions/passiveEventSupported.md new file mode 100644 index 0000000000..85204c9c59 --- /dev/null +++ b/docs/reference/static-functions/functions/passiveEventSupported.md @@ -0,0 +1,26 @@ +--- +id: passiveEventSupported +title: passiveEventSupported +--- + +# Function: passiveEventSupported() + +```ts +function passiveEventSupported(): boolean; +``` + +Defined in: [features/column-resizing/columnResizingFeature.utils.ts:320](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.utils.ts#L320) + +Detects whether the current environment supports passive event listeners. + +Column resizing uses this to register pointer and touch listeners with the safest available options. + +## Returns + +`boolean` + +## Example + +```ts +const canUsePassiveListeners = passiveEventSupported() +``` diff --git a/docs/reference/static-functions/functions/row_getAllCells.md b/docs/reference/static-functions/functions/row_getAllCells.md new file mode 100644 index 0000000000..990340b900 --- /dev/null +++ b/docs/reference/static-functions/functions/row_getAllCells.md @@ -0,0 +1,42 @@ +--- +id: row_getAllCells +title: row_getAllCells +--- + +# Function: row\_getAllCells() + +```ts +function row_getAllCells(row): Cell[]; +``` + +Defined in: [core/rows/coreRowsFeature.utils.ts:163](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.utils.ts#L163) + +Returns all cells for a row. + +This is the static implementation behind the matching row instance API and may read row caches or table state atoms. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +## Returns + +[`Cell`](../../index/type-aliases/Cell.md)\<`TFeatures`, `TData`, `unknown`\>[] + +## Example + +```ts +const value = row_getAllCells(row) +``` diff --git a/docs/reference/static-functions/functions/row_getAllCellsByColumnId.md b/docs/reference/static-functions/functions/row_getAllCellsByColumnId.md new file mode 100644 index 0000000000..c40ec7e372 --- /dev/null +++ b/docs/reference/static-functions/functions/row_getAllCellsByColumnId.md @@ -0,0 +1,42 @@ +--- +id: row_getAllCellsByColumnId +title: row_getAllCellsByColumnId +--- + +# Function: row\_getAllCellsByColumnId() + +```ts +function row_getAllCellsByColumnId(row): Record>; +``` + +Defined in: [core/rows/coreRowsFeature.utils.ts:182](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.utils.ts#L182) + +Returns all cells by column id for a row. + +This is the static implementation behind the matching row instance API and may read row caches or table state atoms. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +## Returns + +`Record`\<`string`, [`Cell`](../../index/type-aliases/Cell.md)\<`TFeatures`, `TData`, `unknown`\>\> + +## Example + +```ts +const value = row_getAllCellsByColumnId(row) +``` diff --git a/docs/reference/static-functions/functions/row_getAllVisibleCells.md b/docs/reference/static-functions/functions/row_getAllVisibleCells.md new file mode 100644 index 0000000000..90bb50b07a --- /dev/null +++ b/docs/reference/static-functions/functions/row_getAllVisibleCells.md @@ -0,0 +1,42 @@ +--- +id: row_getAllVisibleCells +title: row_getAllVisibleCells +--- + +# Function: row\_getAllVisibleCells() + +```ts +function row_getAllVisibleCells(row): Cell[]; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.utils.ts:128](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.utils.ts#L128) + +Returns all visible cells for a row. + +This is the static implementation behind the matching row instance API and may read row caches or table state atoms. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +## Returns + +[`Cell`](../../index/type-aliases/Cell.md)\<`TFeatures`, `TData`, `unknown`\>[] + +## Example + +```ts +const value = row_getAllVisibleCells(row) +``` diff --git a/docs/reference/static-functions/functions/row_getCanExpand.md b/docs/reference/static-functions/functions/row_getCanExpand.md new file mode 100644 index 0000000000..edc84cef82 --- /dev/null +++ b/docs/reference/static-functions/functions/row_getCanExpand.md @@ -0,0 +1,42 @@ +--- +id: row_getCanExpand +title: row_getCanExpand +--- + +# Function: row\_getCanExpand() + +```ts +function row_getCanExpand(row): boolean; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:304](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L304) + +Returns whether a row can use expand. + +This evaluates row data, table options, and feature-specific enablement rules. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = row_getCanExpand(row) +``` diff --git a/docs/reference/static-functions/functions/row_getCanMultiSelect.md b/docs/reference/static-functions/functions/row_getCanMultiSelect.md new file mode 100644 index 0000000000..e7349fbb63 --- /dev/null +++ b/docs/reference/static-functions/functions/row_getCanMultiSelect.md @@ -0,0 +1,42 @@ +--- +id: row_getCanMultiSelect +title: row_getCanMultiSelect +--- + +# Function: row\_getCanMultiSelect() + +```ts +function row_getCanMultiSelect(row): boolean; +``` + +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:541](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L541) + +Returns whether a row can use multi select. + +This evaluates row data, table options, and feature-specific enablement rules. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = row_getCanMultiSelect(row) +``` diff --git a/docs/reference/static-functions/functions/row_getCanPin.md b/docs/reference/static-functions/functions/row_getCanPin.md new file mode 100644 index 0000000000..948d7a9126 --- /dev/null +++ b/docs/reference/static-functions/functions/row_getCanPin.md @@ -0,0 +1,42 @@ +--- +id: row_getCanPin +title: row_getCanPin +--- + +# Function: row\_getCanPin() + +```ts +function row_getCanPin(row): boolean; +``` + +Defined in: [features/row-pinning/rowPinningFeature.utils.ts:202](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.utils.ts#L202) + +Returns whether a row can use pin. + +This evaluates row data, table options, and feature-specific enablement rules. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = row_getCanPin(row) +``` diff --git a/docs/reference/static-functions/functions/row_getCanSelect.md b/docs/reference/static-functions/functions/row_getCanSelect.md new file mode 100644 index 0000000000..ba78a26c9e --- /dev/null +++ b/docs/reference/static-functions/functions/row_getCanSelect.md @@ -0,0 +1,42 @@ +--- +id: row_getCanSelect +title: row_getCanSelect +--- + +# Function: row\_getCanSelect() + +```ts +function row_getCanSelect(row): boolean; +``` + +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:497](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L497) + +Returns whether a row can use select. + +This evaluates row data, table options, and feature-specific enablement rules. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = row_getCanSelect(row) +``` diff --git a/docs/reference/static-functions/functions/row_getCanSelectSubRows.md b/docs/reference/static-functions/functions/row_getCanSelectSubRows.md new file mode 100644 index 0000000000..1e814f9394 --- /dev/null +++ b/docs/reference/static-functions/functions/row_getCanSelectSubRows.md @@ -0,0 +1,42 @@ +--- +id: row_getCanSelectSubRows +title: row_getCanSelectSubRows +--- + +# Function: row\_getCanSelectSubRows() + +```ts +function row_getCanSelectSubRows(row): boolean; +``` + +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:519](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L519) + +Returns whether a row can use select sub rows. + +This evaluates row data, table options, and feature-specific enablement rules. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = row_getCanSelectSubRows(row) +``` diff --git a/docs/reference/static-functions/functions/row_getCenterVisibleCells.md b/docs/reference/static-functions/functions/row_getCenterVisibleCells.md new file mode 100644 index 0000000000..e3743ade0c --- /dev/null +++ b/docs/reference/static-functions/functions/row_getCenterVisibleCells.md @@ -0,0 +1,42 @@ +--- +id: row_getCenterVisibleCells +title: row_getCenterVisibleCells +--- + +# Function: row\_getCenterVisibleCells() + +```ts +function row_getCenterVisibleCells(row): any[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:178](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L178) + +Returns center visible cells for a row. + +This is the static implementation behind the matching row instance API and may read row caches or table state atoms. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +## Returns + +`any`[] + +## Example + +```ts +const value = row_getCenterVisibleCells(row) +``` diff --git a/docs/reference/static-functions/functions/row_getGroupingValue.md b/docs/reference/static-functions/functions/row_getGroupingValue.md new file mode 100644 index 0000000000..77bd6e0289 --- /dev/null +++ b/docs/reference/static-functions/functions/row_getGroupingValue.md @@ -0,0 +1,53 @@ +--- +id: row_getGroupingValue +title: row_getGroupingValue +--- + +# Function: row\_getGroupingValue() + +```ts +function row_getGroupingValue(row, columnId): any; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:258](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L258) + +Returns grouping value for a row. + +This is the static implementation behind the matching row instance API and may read row caches or table state atoms. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row_Core`](../../index/interfaces/Row_Core.md)\<`TFeatures`, `TData`\> & [`UnionToIntersection`](../../index/type-aliases/UnionToIntersection.md)\< + \| `"columnFilteringFeature"` *extends* keyof `TFeatures` ? [`Row_ColumnFiltering`](../../index/interfaces/Row_ColumnFiltering.md)\<`TFeatures`, `TData`\> : `never` + \| `"columnGroupingFeature"` *extends* keyof `TFeatures` ? [`Row_ColumnGrouping`](../../index/interfaces/Row_ColumnGrouping.md) : `never` + \| `"columnPinningFeature"` *extends* keyof `TFeatures` ? [`Row_ColumnPinning`](../../index/interfaces/Row_ColumnPinning.md)\<`TFeatures`, `TData`\> : `never` + \| `"columnVisibilityFeature"` *extends* keyof `TFeatures` ? [`Row_ColumnVisibility`](../../index/interfaces/Row_ColumnVisibility.md)\<`TFeatures`, `TData`\> : `never` + \| `"rowExpandingFeature"` *extends* keyof `TFeatures` ? [`Row_RowExpanding`](../../index/interfaces/Row_RowExpanding.md) : `never` + \| `"rowPinningFeature"` *extends* keyof `TFeatures` ? [`Row_RowPinning`](../../index/interfaces/Row_RowPinning.md) : `never` + \| `"rowSelectionFeature"` *extends* keyof `TFeatures` ? [`Row_RowSelection`](../../index/interfaces/Row_RowSelection.md) : `never`\> & [`UnionToIntersection`](../../index/type-aliases/UnionToIntersection.md)\<\{ \[K in string \| number \| symbol\]: K extends "coreReativityFeature" ? never : TFeatures\[K\] extends TableFeature\ ? "Row" extends keyof FeatureConstructorOptions ? FeatureConstructorOptions\[keyof FeatureConstructorOptions & "Row"\] : never : any \}\[keyof `TFeatures`\]\> & [`Row_Plugins`](../../index/interfaces/Row_Plugins.md)\<`TFeatures`, `TData`\> & `Partial`\<[`Row_ColumnGrouping`](../../index/interfaces/Row_ColumnGrouping.md)\> + +### columnId + +`string` + +## Returns + +`any` + +## Example + +```ts +const value = row_getGroupingValue(row) +``` diff --git a/docs/reference/static-functions/functions/row_getIsAllParentsExpanded.md b/docs/reference/static-functions/functions/row_getIsAllParentsExpanded.md new file mode 100644 index 0000000000..4814ab2a95 --- /dev/null +++ b/docs/reference/static-functions/functions/row_getIsAllParentsExpanded.md @@ -0,0 +1,42 @@ +--- +id: row_getIsAllParentsExpanded +title: row_getIsAllParentsExpanded +--- + +# Function: row\_getIsAllParentsExpanded() + +```ts +function row_getIsAllParentsExpanded(row): boolean; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:324](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L324) + +Returns is all parents expanded for a row. + +This is the static implementation behind the matching row instance API and may read row caches or table state atoms. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = row_getIsAllParentsExpanded(row) +``` diff --git a/docs/reference/static-functions/functions/row_getIsAllSubRowsSelected.md b/docs/reference/static-functions/functions/row_getIsAllSubRowsSelected.md new file mode 100644 index 0000000000..59b37c498f --- /dev/null +++ b/docs/reference/static-functions/functions/row_getIsAllSubRowsSelected.md @@ -0,0 +1,42 @@ +--- +id: row_getIsAllSubRowsSelected +title: row_getIsAllSubRowsSelected +--- + +# Function: row\_getIsAllSubRowsSelected() + +```ts +function row_getIsAllSubRowsSelected(row): boolean; +``` + +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:480](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L480) + +Returns is all sub rows selected for a row. + +This is the static implementation behind the matching row instance API and may read row caches or table state atoms. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = row_getIsAllSubRowsSelected(row) +``` diff --git a/docs/reference/static-functions/functions/row_getIsExpanded.md b/docs/reference/static-functions/functions/row_getIsExpanded.md new file mode 100644 index 0000000000..8d8a4d42c4 --- /dev/null +++ b/docs/reference/static-functions/functions/row_getIsExpanded.md @@ -0,0 +1,42 @@ +--- +id: row_getIsExpanded +title: row_getIsExpanded +--- + +# Function: row\_getIsExpanded() + +```ts +function row_getIsExpanded(row): boolean; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:282](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L282) + +Returns is expanded for a row. + +This is the static implementation behind the matching row instance API and may read row caches or table state atoms. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = row_getIsExpanded(row) +``` diff --git a/docs/reference/static-functions/functions/row_getIsGrouped.md b/docs/reference/static-functions/functions/row_getIsGrouped.md new file mode 100644 index 0000000000..86eeef153d --- /dev/null +++ b/docs/reference/static-functions/functions/row_getIsGrouped.md @@ -0,0 +1,49 @@ +--- +id: row_getIsGrouped +title: row_getIsGrouped +--- + +# Function: row\_getIsGrouped() + +```ts +function row_getIsGrouped(row): boolean; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:241](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L241) + +Returns is grouped for a row. + +This is the static implementation behind the matching row instance API and may read row caches or table state atoms. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row_Core`](../../index/interfaces/Row_Core.md)\<`TFeatures`, `TData`\> & [`UnionToIntersection`](../../index/type-aliases/UnionToIntersection.md)\< + \| `"columnFilteringFeature"` *extends* keyof `TFeatures` ? [`Row_ColumnFiltering`](../../index/interfaces/Row_ColumnFiltering.md)\<`TFeatures`, `TData`\> : `never` + \| `"columnGroupingFeature"` *extends* keyof `TFeatures` ? [`Row_ColumnGrouping`](../../index/interfaces/Row_ColumnGrouping.md) : `never` + \| `"columnPinningFeature"` *extends* keyof `TFeatures` ? [`Row_ColumnPinning`](../../index/interfaces/Row_ColumnPinning.md)\<`TFeatures`, `TData`\> : `never` + \| `"columnVisibilityFeature"` *extends* keyof `TFeatures` ? [`Row_ColumnVisibility`](../../index/interfaces/Row_ColumnVisibility.md)\<`TFeatures`, `TData`\> : `never` + \| `"rowExpandingFeature"` *extends* keyof `TFeatures` ? [`Row_RowExpanding`](../../index/interfaces/Row_RowExpanding.md) : `never` + \| `"rowPinningFeature"` *extends* keyof `TFeatures` ? [`Row_RowPinning`](../../index/interfaces/Row_RowPinning.md) : `never` + \| `"rowSelectionFeature"` *extends* keyof `TFeatures` ? [`Row_RowSelection`](../../index/interfaces/Row_RowSelection.md) : `never`\> & [`UnionToIntersection`](../../index/type-aliases/UnionToIntersection.md)\<\{ \[K in string \| number \| symbol\]: K extends "coreReativityFeature" ? never : TFeatures\[K\] extends TableFeature\ ? "Row" extends keyof FeatureConstructorOptions ? FeatureConstructorOptions\[keyof FeatureConstructorOptions & "Row"\] : never : any \}\[keyof `TFeatures`\]\> & [`Row_Plugins`](../../index/interfaces/Row_Plugins.md)\<`TFeatures`, `TData`\> & `Partial`\<[`Row_ColumnGrouping`](../../index/interfaces/Row_ColumnGrouping.md)\> + +## Returns + +`boolean` + +## Example + +```ts +const value = row_getIsGrouped(row) +``` diff --git a/docs/reference/static-functions/functions/row_getIsPinned.md b/docs/reference/static-functions/functions/row_getIsPinned.md new file mode 100644 index 0000000000..213796f684 --- /dev/null +++ b/docs/reference/static-functions/functions/row_getIsPinned.md @@ -0,0 +1,42 @@ +--- +id: row_getIsPinned +title: row_getIsPinned +--- + +# Function: row\_getIsPinned() + +```ts +function row_getIsPinned(row): RowPinningPosition; +``` + +Defined in: [features/row-pinning/rowPinningFeature.utils.ts:223](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.utils.ts#L223) + +Returns is pinned for a row. + +This is the static implementation behind the matching row instance API and may read row caches or table state atoms. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +## Returns + +[`RowPinningPosition`](../../index/type-aliases/RowPinningPosition.md) + +## Example + +```ts +const value = row_getIsPinned(row) +``` diff --git a/docs/reference/static-functions/functions/row_getIsSelected.md b/docs/reference/static-functions/functions/row_getIsSelected.md new file mode 100644 index 0000000000..be994c744e --- /dev/null +++ b/docs/reference/static-functions/functions/row_getIsSelected.md @@ -0,0 +1,42 @@ +--- +id: row_getIsSelected +title: row_getIsSelected +--- + +# Function: row\_getIsSelected() + +```ts +function row_getIsSelected(row): boolean; +``` + +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:446](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L446) + +Returns is selected for a row. + +This is the static implementation behind the matching row instance API and may read row caches or table state atoms. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = row_getIsSelected(row) +``` diff --git a/docs/reference/static-functions/functions/row_getIsSomeSelected.md b/docs/reference/static-functions/functions/row_getIsSomeSelected.md new file mode 100644 index 0000000000..b6de180db0 --- /dev/null +++ b/docs/reference/static-functions/functions/row_getIsSomeSelected.md @@ -0,0 +1,42 @@ +--- +id: row_getIsSomeSelected +title: row_getIsSomeSelected +--- + +# Function: row\_getIsSomeSelected() + +```ts +function row_getIsSomeSelected(row): boolean; +``` + +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:463](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L463) + +Returns is some selected for a row. + +This is the static implementation behind the matching row instance API and may read row caches or table state atoms. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = row_getIsSomeSelected(row) +``` diff --git a/docs/reference/static-functions/functions/row_getLeafRows.md b/docs/reference/static-functions/functions/row_getLeafRows.md new file mode 100644 index 0000000000..cbadc0f4ea --- /dev/null +++ b/docs/reference/static-functions/functions/row_getLeafRows.md @@ -0,0 +1,42 @@ +--- +id: row_getLeafRows +title: row_getLeafRows +--- + +# Function: row\_getLeafRows() + +```ts +function row_getLeafRows(row): Row[]; +``` + +Defined in: [core/rows/coreRowsFeature.utils.ts:103](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.utils.ts#L103) + +Returns leaf rows for a row. + +This is the static implementation behind the matching row instance API and may read row caches or table state atoms. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +## Returns + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\>[] + +## Example + +```ts +const value = row_getLeafRows(row) +``` diff --git a/docs/reference/static-functions/functions/row_getLeftVisibleCells.md b/docs/reference/static-functions/functions/row_getLeftVisibleCells.md new file mode 100644 index 0000000000..81bf5e8b47 --- /dev/null +++ b/docs/reference/static-functions/functions/row_getLeftVisibleCells.md @@ -0,0 +1,42 @@ +--- +id: row_getLeftVisibleCells +title: row_getLeftVisibleCells +--- + +# Function: row\_getLeftVisibleCells() + +```ts +function row_getLeftVisibleCells(row): Cell[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:203](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L203) + +Returns left visible cells for a row. + +This is the static implementation behind the matching row instance API and may read row caches or table state atoms. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +## Returns + +[`Cell`](../../index/type-aliases/Cell.md)\<`TFeatures`, `TData`, `unknown`\>[] + +## Example + +```ts +const value = row_getLeftVisibleCells(row) +``` diff --git a/docs/reference/static-functions/functions/row_getParentRow.md b/docs/reference/static-functions/functions/row_getParentRow.md new file mode 100644 index 0000000000..c333cb8747 --- /dev/null +++ b/docs/reference/static-functions/functions/row_getParentRow.md @@ -0,0 +1,45 @@ +--- +id: row_getParentRow +title: row_getParentRow +--- + +# Function: row\_getParentRow() + +```ts +function row_getParentRow(row): + | Row + | undefined; +``` + +Defined in: [core/rows/coreRowsFeature.utils.ts:120](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.utils.ts#L120) + +Returns parent row for a row. + +This is the static implementation behind the matching row instance API and may read row caches or table state atoms. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +## Returns + + \| [`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + \| `undefined` + +## Example + +```ts +const value = row_getParentRow(row) +``` diff --git a/docs/reference/static-functions/functions/row_getParentRows.md b/docs/reference/static-functions/functions/row_getParentRows.md new file mode 100644 index 0000000000..e3da7693a1 --- /dev/null +++ b/docs/reference/static-functions/functions/row_getParentRows.md @@ -0,0 +1,42 @@ +--- +id: row_getParentRows +title: row_getParentRows +--- + +# Function: row\_getParentRows() + +```ts +function row_getParentRows(row): Row[]; +``` + +Defined in: [core/rows/coreRowsFeature.utils.ts:137](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.utils.ts#L137) + +Returns parent rows for a row. + +This is the static implementation behind the matching row instance API and may read row caches or table state atoms. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +## Returns + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\>[] + +## Example + +```ts +const value = row_getParentRows(row) +``` diff --git a/docs/reference/static-functions/functions/row_getPinnedIndex.md b/docs/reference/static-functions/functions/row_getPinnedIndex.md new file mode 100644 index 0000000000..38a1a0e33a --- /dev/null +++ b/docs/reference/static-functions/functions/row_getPinnedIndex.md @@ -0,0 +1,42 @@ +--- +id: row_getPinnedIndex +title: row_getPinnedIndex +--- + +# Function: row\_getPinnedIndex() + +```ts +function row_getPinnedIndex(row): number; +``` + +Defined in: [features/row-pinning/rowPinningFeature.utils.ts:247](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.utils.ts#L247) + +Returns pinned index for a row. + +This is the static implementation behind the matching row instance API and may read row caches or table state atoms. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +## Returns + +`number` + +## Example + +```ts +const value = row_getPinnedIndex(row) +``` diff --git a/docs/reference/static-functions/functions/row_getRightVisibleCells.md b/docs/reference/static-functions/functions/row_getRightVisibleCells.md new file mode 100644 index 0000000000..6659ff08d0 --- /dev/null +++ b/docs/reference/static-functions/functions/row_getRightVisibleCells.md @@ -0,0 +1,42 @@ +--- +id: row_getRightVisibleCells +title: row_getRightVisibleCells +--- + +# Function: row\_getRightVisibleCells() + +```ts +function row_getRightVisibleCells(row): any; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:234](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L234) + +Returns right visible cells for a row. + +This is the static implementation behind the matching row instance API and may read row caches or table state atoms. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +## Returns + +`any` + +## Example + +```ts +const value = row_getRightVisibleCells(row) +``` diff --git a/docs/reference/static-functions/functions/row_getToggleExpandedHandler.md b/docs/reference/static-functions/functions/row_getToggleExpandedHandler.md new file mode 100644 index 0000000000..353c7d2bd5 --- /dev/null +++ b/docs/reference/static-functions/functions/row_getToggleExpandedHandler.md @@ -0,0 +1,48 @@ +--- +id: row_getToggleExpandedHandler +title: row_getToggleExpandedHandler +--- + +# Function: row\_getToggleExpandedHandler() + +```ts +function row_getToggleExpandedHandler(row): () => void; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:349](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L349) + +Returns an event handler for toggling expanded handler. + +The handler is intended for direct use in row-level controls such as expansion or selection buttons. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +## Returns + +```ts +(): void; +``` + +### Returns + +`void` + +## Example + +```ts +const value = row_getToggleExpandedHandler(row) +``` diff --git a/docs/reference/static-functions/functions/row_getToggleSelectedHandler.md b/docs/reference/static-functions/functions/row_getToggleSelectedHandler.md new file mode 100644 index 0000000000..23bdd645ca --- /dev/null +++ b/docs/reference/static-functions/functions/row_getToggleSelectedHandler.md @@ -0,0 +1,54 @@ +--- +id: row_getToggleSelectedHandler +title: row_getToggleSelectedHandler +--- + +# Function: row\_getToggleSelectedHandler() + +```ts +function row_getToggleSelectedHandler(row): (e) => void; +``` + +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:563](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L563) + +Returns an event handler for toggling selected handler. + +The handler is intended for direct use in row-level controls such as expansion or selection buttons. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +## Returns + +```ts +(e): void; +``` + +### Parameters + +#### e + +`unknown` + +### Returns + +`void` + +## Example + +```ts +const value = row_getToggleSelectedHandler(row) +``` diff --git a/docs/reference/static-functions/functions/row_getUniqueValues.md b/docs/reference/static-functions/functions/row_getUniqueValues.md new file mode 100644 index 0000000000..541fd8152d --- /dev/null +++ b/docs/reference/static-functions/functions/row_getUniqueValues.md @@ -0,0 +1,46 @@ +--- +id: row_getUniqueValues +title: row_getUniqueValues +--- + +# Function: row\_getUniqueValues() + +```ts +function row_getUniqueValues(row, columnId): unknown; +``` + +Defined in: [core/rows/coreRowsFeature.utils.ts:48](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.utils.ts#L48) + +Returns unique values for a row. + +This is the static implementation behind the matching row instance API and may read row caches or table state atoms. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +### columnId + +`string` + +## Returns + +`unknown` + +## Example + +```ts +const value = row_getUniqueValues(row) +``` diff --git a/docs/reference/static-functions/functions/row_getValue.md b/docs/reference/static-functions/functions/row_getValue.md new file mode 100644 index 0000000000..bdeb6fb7de --- /dev/null +++ b/docs/reference/static-functions/functions/row_getValue.md @@ -0,0 +1,46 @@ +--- +id: row_getValue +title: row_getValue +--- + +# Function: row\_getValue() + +```ts +function row_getValue(row, columnId): unknown; +``` + +Defined in: [core/rows/coreRowsFeature.utils.ts:19](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.utils.ts#L19) + +Returns value for a row. + +This is the static implementation behind the matching row instance API and may read row caches or table state atoms. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +### columnId + +`string` + +## Returns + +`unknown` + +## Example + +```ts +const value = row_getValue(row) +``` diff --git a/docs/reference/static-functions/functions/row_getVisibleCells.md b/docs/reference/static-functions/functions/row_getVisibleCells.md new file mode 100644 index 0000000000..93e6b595a7 --- /dev/null +++ b/docs/reference/static-functions/functions/row_getVisibleCells.md @@ -0,0 +1,53 @@ +--- +id: row_getVisibleCells +title: row_getVisibleCells +--- + +# Function: row\_getVisibleCells() + +```ts +function row_getVisibleCells( + left, + center, + right): Cell[]; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.utils.ts:149](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.utils.ts#L149) + +Returns visible cells for a row. + +This is the static implementation behind the matching row instance API and may read row caches or table state atoms. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### left + +[`Cell`](../../index/type-aliases/Cell.md)\<`TFeatures`, `TData`, `unknown`\>[] + +### center + +[`Cell`](../../index/type-aliases/Cell.md)\<`TFeatures`, `TData`, `unknown`\>[] + +### right + +[`Cell`](../../index/type-aliases/Cell.md)\<`TFeatures`, `TData`, `unknown`\>[] + +## Returns + +[`Cell`](../../index/type-aliases/Cell.md)\<`TFeatures`, `TData`, `unknown`\>[] + +## Example + +```ts +const value = row_getVisibleCells(row) +``` diff --git a/docs/reference/static-functions/functions/row_pin.md b/docs/reference/static-functions/functions/row_pin.md new file mode 100644 index 0000000000..1d2ed704fe --- /dev/null +++ b/docs/reference/static-functions/functions/row_pin.md @@ -0,0 +1,59 @@ +--- +id: row_pin +title: row_pin +--- + +# Function: row\_pin() + +```ts +function row_pin( + row, + position, + includeLeafRows?, + includeParentRows?): void; +``` + +Defined in: [features/row-pinning/rowPinningFeature.utils.ts:274](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.utils.ts#L274) + +Pins or unpins a row. + +Optional flags let callers include parent rows or leaf rows when updating +the row pinning state. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +### position + +[`RowPinningPosition`](../../index/type-aliases/RowPinningPosition.md) + +### includeLeafRows? + +`boolean` + +### includeParentRows? + +`boolean` + +## Returns + +`void` + +## Example + +```ts +row_pin(row, 'top') +``` diff --git a/docs/reference/static-functions/functions/row_renderValue.md b/docs/reference/static-functions/functions/row_renderValue.md new file mode 100644 index 0000000000..83beb9f378 --- /dev/null +++ b/docs/reference/static-functions/functions/row_renderValue.md @@ -0,0 +1,47 @@ +--- +id: row_renderValue +title: row_renderValue +--- + +# Function: row\_renderValue() + +```ts +function row_renderValue(row, columnId): any; +``` + +Defined in: [core/rows/coreRowsFeature.utils.ts:86](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.utils.ts#L86) + +Returns a renderable row value for a column. + +If the accessor value is nullish, the table's `renderFallbackValue` is used +instead. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +### columnId + +`string` + +## Returns + +`any` + +## Example + +```ts +const value = row_renderValue(row) +``` diff --git a/docs/reference/static-functions/functions/row_toggleExpanded.md b/docs/reference/static-functions/functions/row_toggleExpanded.md new file mode 100644 index 0000000000..63e5129dcc --- /dev/null +++ b/docs/reference/static-functions/functions/row_toggleExpanded.md @@ -0,0 +1,46 @@ +--- +id: row_toggleExpanded +title: row_toggleExpanded +--- + +# Function: row\_toggleExpanded() + +```ts +function row_toggleExpanded(row, expanded?): void; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:237](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L237) + +Toggles expanded for a row. + +The update is routed through the table state updater for the owning feature state slice. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +### expanded? + +`boolean` + +## Returns + +`void` + +## Example + +```ts +row_toggleExpanded(row) +``` diff --git a/docs/reference/static-functions/functions/row_toggleSelected.md b/docs/reference/static-functions/functions/row_toggleSelected.md new file mode 100644 index 0000000000..866902c78d --- /dev/null +++ b/docs/reference/static-functions/functions/row_toggleSelected.md @@ -0,0 +1,55 @@ +--- +id: row_toggleSelected +title: row_toggleSelected +--- + +# Function: row\_toggleSelected() + +```ts +function row_toggleSelected( + row, + value?, + opts?): void; +``` + +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:403](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L403) + +Toggles selected for a row. + +The update is routed through the table state updater for the owning feature state slice. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### row + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +### value? + +`boolean` + +### opts? + +#### selectChildren? + +`boolean` + +## Returns + +`void` + +## Example + +```ts +row_toggleSelected(row) +``` diff --git a/docs/reference/static-functions/functions/selectRowsFn.md b/docs/reference/static-functions/functions/selectRowsFn.md new file mode 100644 index 0000000000..4bfa00fa54 --- /dev/null +++ b/docs/reference/static-functions/functions/selectRowsFn.md @@ -0,0 +1,42 @@ +--- +id: selectRowsFn +title: selectRowsFn +--- + +# Function: selectRowsFn() + +```ts +function selectRowsFn(rowModel): RowModel; +``` + +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:618](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L618) + +Builds a row model containing rows selected by the current row selection state. + +The result is derived from the supplied row model, so selected ids absent from the current data are not materialized as rows. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### rowModel + +[`RowModel`](../../index/interfaces/RowModel.md)\<`TFeatures`, `TData`\> + +## Returns + +[`RowModel`](../../index/interfaces/RowModel.md)\<`TFeatures`, `TData`\> + +## Example + +```ts +const selectedRows = selectRowsFn(table, rowModel) +``` diff --git a/docs/reference/static-functions/functions/shouldAutoRemoveFilter.md b/docs/reference/static-functions/functions/shouldAutoRemoveFilter.md new file mode 100644 index 0000000000..d6968c52ce --- /dev/null +++ b/docs/reference/static-functions/functions/shouldAutoRemoveFilter.md @@ -0,0 +1,57 @@ +--- +id: shouldAutoRemoveFilter +title: shouldAutoRemoveFilter +--- + +# Function: shouldAutoRemoveFilter() + +```ts +function shouldAutoRemoveFilter( + filterFn?, + value?, + column?): boolean; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:304](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L304) + +Returns whether a filter value should be removed from filter state. + +This checks the filter function's `autoRemove` hook and built-in empty-value rules. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +### TValue + +`TValue` *extends* `unknown` = `unknown` + +## Parameters + +### filterFn? + +[`FilterFn`](../../index/interfaces/FilterFn.md)\<`TFeatures`, `TData`\> + +### value? + +`any` + +### column? + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `TValue`\> + +## Returns + +`boolean` + +## Example + +```ts +const removeFilter = shouldAutoRemoveFilter(filterFn, value, column) +``` diff --git a/docs/reference/static-functions/functions/table_autoResetExpanded.md b/docs/reference/static-functions/functions/table_autoResetExpanded.md new file mode 100644 index 0000000000..7691b734c5 --- /dev/null +++ b/docs/reference/static-functions/functions/table_autoResetExpanded.md @@ -0,0 +1,42 @@ +--- +id: table_autoResetExpanded +title: table_autoResetExpanded +--- + +# Function: table\_autoResetExpanded() + +```ts +function table_autoResetExpanded(table): void; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:35](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L35) + +Schedules an automatic reset for expanded. + +The reset only runs when the related feature options allow automatic resets for the current table state change. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`void` + +## Example + +```ts +table_autoResetExpanded(table) +``` diff --git a/docs/reference/static-functions/functions/table_autoResetPageIndex.md b/docs/reference/static-functions/functions/table_autoResetPageIndex.md new file mode 100644 index 0000000000..a17ffe04b9 --- /dev/null +++ b/docs/reference/static-functions/functions/table_autoResetPageIndex.md @@ -0,0 +1,42 @@ +--- +id: table_autoResetPageIndex +title: table_autoResetPageIndex +--- + +# Function: table\_autoResetPageIndex() + +```ts +function table_autoResetPageIndex(table): void; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.utils.ts:37](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.utils.ts#L37) + +Schedules an automatic reset for page index. + +The reset only runs when the related feature options allow automatic resets for the current table state change. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`void` + +## Example + +```ts +table_autoResetPageIndex(table) +``` diff --git a/docs/reference/static-functions/functions/table_firstPage.md b/docs/reference/static-functions/functions/table_firstPage.md new file mode 100644 index 0000000000..5e8bb08a46 --- /dev/null +++ b/docs/reference/static-functions/functions/table_firstPage.md @@ -0,0 +1,42 @@ +--- +id: table_firstPage +title: table_firstPage +--- + +# Function: table\_firstPage() + +```ts +function table_firstPage(table): void; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.utils.ts:321](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.utils.ts#L321) + +Moves the table to the first page. + +This is a convenience wrapper around `table_setPageIndex(table, 0)`. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`void` + +## Example + +```ts +table_firstPage(table) +``` diff --git a/docs/reference/static-functions/functions/table_getAllColumns.md b/docs/reference/static-functions/functions/table_getAllColumns.md new file mode 100644 index 0000000000..71fc12521a --- /dev/null +++ b/docs/reference/static-functions/functions/table_getAllColumns.md @@ -0,0 +1,42 @@ +--- +id: table_getAllColumns +title: table_getAllColumns +--- + +# Function: table\_getAllColumns() + +```ts +function table_getAllColumns(table): Column[]; +``` + +Defined in: [core/columns/coreColumnsFeature.utils.ts:115](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts#L115) + +Returns all columns for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`Column`](../../index/type-aliases/Column.md)\<`TFeatures`, `TData`, `unknown`\>[] + +## Example + +```ts +const value = table_getAllColumns(table) +``` diff --git a/docs/reference/static-functions/functions/table_getAllFlatColumns.md b/docs/reference/static-functions/functions/table_getAllFlatColumns.md new file mode 100644 index 0000000000..943c5df0c5 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getAllFlatColumns.md @@ -0,0 +1,42 @@ +--- +id: table_getAllFlatColumns +title: table_getAllFlatColumns +--- + +# Function: table\_getAllFlatColumns() + +```ts +function table_getAllFlatColumns(table): Column[]; +``` + +Defined in: [core/columns/coreColumnsFeature.utils.ts:156](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts#L156) + +Returns all flat columns for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`Column`](../../index/type-aliases/Column.md)\<`TFeatures`, `TData`, `unknown`\>[] + +## Example + +```ts +const value = table_getAllFlatColumns(table) +``` diff --git a/docs/reference/static-functions/functions/table_getAllFlatColumnsById.md b/docs/reference/static-functions/functions/table_getAllFlatColumnsById.md new file mode 100644 index 0000000000..02d25107a5 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getAllFlatColumnsById.md @@ -0,0 +1,42 @@ +--- +id: table_getAllFlatColumnsById +title: table_getAllFlatColumnsById +--- + +# Function: table\_getAllFlatColumnsById() + +```ts +function table_getAllFlatColumnsById(table): Record>; +``` + +Defined in: [core/columns/coreColumnsFeature.utils.ts:175](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts#L175) + +Returns all flat columns by id for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`Record`\<`string`, [`Column`](../../index/type-aliases/Column.md)\<`TFeatures`, `TData`, `unknown`\>\> + +## Example + +```ts +const value = table_getAllFlatColumnsById(table) +``` diff --git a/docs/reference/static-functions/functions/table_getAllLeafColumns.md b/docs/reference/static-functions/functions/table_getAllLeafColumns.md new file mode 100644 index 0000000000..a907b5c3d9 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getAllLeafColumns.md @@ -0,0 +1,42 @@ +--- +id: table_getAllLeafColumns +title: table_getAllLeafColumns +--- + +# Function: table\_getAllLeafColumns() + +```ts +function table_getAllLeafColumns(table): Column[]; +``` + +Defined in: [core/columns/coreColumnsFeature.utils.ts:200](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts#L200) + +Returns all leaf columns for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`Column`](../../index/type-aliases/Column.md)\<`TFeatures`, `TData`, `unknown`\>[] + +## Example + +```ts +const value = table_getAllLeafColumns(table) +``` diff --git a/docs/reference/static-functions/functions/table_getBottomRows.md b/docs/reference/static-functions/functions/table_getBottomRows.md new file mode 100644 index 0000000000..4d5ffabb76 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getBottomRows.md @@ -0,0 +1,42 @@ +--- +id: table_getBottomRows +title: table_getBottomRows +--- + +# Function: table\_getBottomRows() + +```ts +function table_getBottomRows(table): Row[]; +``` + +Defined in: [features/row-pinning/rowPinningFeature.utils.ts:161](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.utils.ts#L161) + +Returns bottom rows for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\>[] + +## Example + +```ts +const value = table_getBottomRows(table) +``` diff --git a/docs/reference/static-functions/functions/table_getCanNextPage.md b/docs/reference/static-functions/functions/table_getCanNextPage.md new file mode 100644 index 0000000000..afa4a6e955 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getCanNextPage.md @@ -0,0 +1,42 @@ +--- +id: table_getCanNextPage +title: table_getCanNextPage +--- + +# Function: table\_getCanNextPage() + +```ts +function table_getCanNextPage(table): boolean; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.utils.ts:254](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.utils.ts#L254) + +Returns can next page for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = table_getCanNextPage(table) +``` diff --git a/docs/reference/static-functions/functions/table_getCanPreviousPage.md b/docs/reference/static-functions/functions/table_getCanPreviousPage.md new file mode 100644 index 0000000000..6aa8a9868b --- /dev/null +++ b/docs/reference/static-functions/functions/table_getCanPreviousPage.md @@ -0,0 +1,42 @@ +--- +id: table_getCanPreviousPage +title: table_getCanPreviousPage +--- + +# Function: table\_getCanPreviousPage() + +```ts +function table_getCanPreviousPage(table): boolean; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.utils.ts:237](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.utils.ts#L237) + +Returns can previous page for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = table_getCanPreviousPage(table) +``` diff --git a/docs/reference/static-functions/functions/table_getCanSomeRowsExpand.md b/docs/reference/static-functions/functions/table_getCanSomeRowsExpand.md new file mode 100644 index 0000000000..5fe84f9e66 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getCanSomeRowsExpand.md @@ -0,0 +1,42 @@ +--- +id: table_getCanSomeRowsExpand +title: table_getCanSomeRowsExpand +--- + +# Function: table\_getCanSomeRowsExpand() + +```ts +function table_getCanSomeRowsExpand(table): boolean; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:117](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L117) + +Returns can some rows expand for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = table_getCanSomeRowsExpand(table) +``` diff --git a/docs/reference/static-functions/functions/table_getCenterFlatHeaders.md b/docs/reference/static-functions/functions/table_getCenterFlatHeaders.md new file mode 100644 index 0000000000..ceab2cf1ac --- /dev/null +++ b/docs/reference/static-functions/functions/table_getCenterFlatHeaders.md @@ -0,0 +1,42 @@ +--- +id: table_getCenterFlatHeaders +title: table_getCenterFlatHeaders +--- + +# Function: table\_getCenterFlatHeaders() + +```ts +function table_getCenterFlatHeaders(table): any[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:550](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L550) + +Returns center flat headers for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`any`[] + +## Example + +```ts +const value = table_getCenterFlatHeaders(table) +``` diff --git a/docs/reference/static-functions/functions/table_getCenterFooterGroups.md b/docs/reference/static-functions/functions/table_getCenterFooterGroups.md new file mode 100644 index 0000000000..a1946d8ed8 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getCenterFooterGroups.md @@ -0,0 +1,42 @@ +--- +id: table_getCenterFooterGroups +title: table_getCenterFooterGroups +--- + +# Function: table\_getCenterFooterGroups() + +```ts +function table_getCenterFooterGroups(table): any[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:474](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L474) + +Returns center footer groups for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`any`[] + +## Example + +```ts +const value = table_getCenterFooterGroups(table) +``` diff --git a/docs/reference/static-functions/functions/table_getCenterHeaderGroups.md b/docs/reference/static-functions/functions/table_getCenterHeaderGroups.md new file mode 100644 index 0000000000..4e75904ce3 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getCenterHeaderGroups.md @@ -0,0 +1,42 @@ +--- +id: table_getCenterHeaderGroups +title: table_getCenterHeaderGroups +--- + +# Function: table\_getCenterHeaderGroups() + +```ts +function table_getCenterHeaderGroups(table): HeaderGroup[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:396](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L396) + +Returns center header groups for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`HeaderGroup`](../../index/type-aliases/HeaderGroup.md)\<`TFeatures`, `TData`\>[] + +## Example + +```ts +const value = table_getCenterHeaderGroups(table) +``` diff --git a/docs/reference/static-functions/functions/table_getCenterLeafColumns.md b/docs/reference/static-functions/functions/table_getCenterLeafColumns.md new file mode 100644 index 0000000000..b7f3f15752 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getCenterLeafColumns.md @@ -0,0 +1,42 @@ +--- +id: table_getCenterLeafColumns +title: table_getCenterLeafColumns +--- + +# Function: table\_getCenterLeafColumns() + +```ts +function table_getCenterLeafColumns(table): Column[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:691](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L691) + +Returns center leaf columns for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`Column`](../../index/type-aliases/Column.md)\<`TFeatures`, `TData`, `unknown`\>[] + +## Example + +```ts +const value = table_getCenterLeafColumns(table) +``` diff --git a/docs/reference/static-functions/functions/table_getCenterLeafHeaders.md b/docs/reference/static-functions/functions/table_getCenterLeafHeaders.md new file mode 100644 index 0000000000..332ef5b78e --- /dev/null +++ b/docs/reference/static-functions/functions/table_getCenterLeafHeaders.md @@ -0,0 +1,42 @@ +--- +id: table_getCenterLeafHeaders +title: table_getCenterLeafHeaders +--- + +# Function: table\_getCenterLeafHeaders() + +```ts +function table_getCenterLeafHeaders(table): any[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:620](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L620) + +Returns center leaf headers for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`any`[] + +## Example + +```ts +const value = table_getCenterLeafHeaders(table) +``` diff --git a/docs/reference/static-functions/functions/table_getCenterRows.md b/docs/reference/static-functions/functions/table_getCenterRows.md new file mode 100644 index 0000000000..43ef1721b8 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getCenterRows.md @@ -0,0 +1,42 @@ +--- +id: table_getCenterRows +title: table_getCenterRows +--- + +# Function: table\_getCenterRows() + +```ts +function table_getCenterRows(table): Row[]; +``` + +Defined in: [features/row-pinning/rowPinningFeature.utils.ts:178](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.utils.ts#L178) + +Returns center rows for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\>[] + +## Example + +```ts +const value = table_getCenterRows(table) +``` diff --git a/docs/reference/static-functions/functions/table_getCenterTotalSize.md b/docs/reference/static-functions/functions/table_getCenterTotalSize.md new file mode 100644 index 0000000000..a1ca8be2ea --- /dev/null +++ b/docs/reference/static-functions/functions/table_getCenterTotalSize.md @@ -0,0 +1,42 @@ +--- +id: table_getCenterTotalSize +title: table_getCenterTotalSize +--- + +# Function: table\_getCenterTotalSize() + +```ts +function table_getCenterTotalSize(table): any; +``` + +Defined in: [features/column-sizing/columnSizingFeature.utils.ts:314](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.utils.ts#L314) + +Returns center total size for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`any` + +## Example + +```ts +const value = table_getCenterTotalSize(table) +``` diff --git a/docs/reference/static-functions/functions/table_getCenterVisibleLeafColumns.md b/docs/reference/static-functions/functions/table_getCenterVisibleLeafColumns.md new file mode 100644 index 0000000000..2418dd166d --- /dev/null +++ b/docs/reference/static-functions/functions/table_getCenterVisibleLeafColumns.md @@ -0,0 +1,42 @@ +--- +id: table_getCenterVisibleLeafColumns +title: table_getCenterVisibleLeafColumns +--- + +# Function: table\_getCenterVisibleLeafColumns() + +```ts +function table_getCenterVisibleLeafColumns(table): any[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:797](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L797) + +Returns center visible leaf columns for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`any`[] + +## Example + +```ts +const value = table_getCenterVisibleLeafColumns(table) +``` diff --git a/docs/reference/static-functions/functions/table_getColumn.md b/docs/reference/static-functions/functions/table_getColumn.md new file mode 100644 index 0000000000..576eede2f9 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getColumn.md @@ -0,0 +1,49 @@ +--- +id: table_getColumn +title: table_getColumn +--- + +# Function: table\_getColumn() + +```ts +function table_getColumn(table, columnId): + | Column + | undefined; +``` + +Defined in: [core/columns/coreColumnsFeature.utils.ts:226](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts#L226) + +Returns column for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### columnId + +`string` + +## Returns + + \| [`Column`](../../index/type-aliases/Column.md)\<`TFeatures`, `TData`, `unknown`\> + \| `undefined` + +## Example + +```ts +const value = table_getColumn(table) +``` diff --git a/docs/reference/static-functions/functions/table_getCoreRowModel.md b/docs/reference/static-functions/functions/table_getCoreRowModel.md new file mode 100644 index 0000000000..5d233bcbb1 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getCoreRowModel.md @@ -0,0 +1,42 @@ +--- +id: table_getCoreRowModel +title: table_getCoreRowModel +--- + +# Function: table\_getCoreRowModel() + +```ts +function table_getCoreRowModel(table): RowModel; +``` + +Defined in: [core/row-models/coreRowModelsFeature.utils.ts:17](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.utils.ts#L17) + +Returns core row model for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`RowModel`](../../index/interfaces/RowModel.md)\<`TFeatures`, `TData`\> + +## Example + +```ts +const value = table_getCoreRowModel(table) +``` diff --git a/docs/reference/static-functions/functions/table_getDefaultColumnDef.md b/docs/reference/static-functions/functions/table_getDefaultColumnDef.md new file mode 100644 index 0000000000..d77e02de8f --- /dev/null +++ b/docs/reference/static-functions/functions/table_getDefaultColumnDef.md @@ -0,0 +1,42 @@ +--- +id: table_getDefaultColumnDef +title: table_getDefaultColumnDef +--- + +# Function: table\_getDefaultColumnDef() + +```ts +function table_getDefaultColumnDef(table): Partial>; +``` + +Defined in: [core/columns/coreColumnsFeature.utils.ts:76](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts#L76) + +Returns default column def for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`Partial`\<[`ColumnDef`](../../index/type-aliases/ColumnDef.md)\<`TFeatures`, `TData`, `unknown`\>\> + +## Example + +```ts +const value = table_getDefaultColumnDef(table) +``` diff --git a/docs/reference/static-functions/functions/table_getExpandedDepth.md b/docs/reference/static-functions/functions/table_getExpandedDepth.md new file mode 100644 index 0000000000..1aa1ddf528 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getExpandedDepth.md @@ -0,0 +1,42 @@ +--- +id: table_getExpandedDepth +title: table_getExpandedDepth +--- + +# Function: table\_getExpandedDepth() + +```ts +function table_getExpandedDepth(table): number; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:208](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L208) + +Returns expanded depth for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`number` + +## Example + +```ts +const value = table_getExpandedDepth(table) +``` diff --git a/docs/reference/static-functions/functions/table_getExpandedRowModel.md b/docs/reference/static-functions/functions/table_getExpandedRowModel.md new file mode 100644 index 0000000000..471768e182 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getExpandedRowModel.md @@ -0,0 +1,42 @@ +--- +id: table_getExpandedRowModel +title: table_getExpandedRowModel +--- + +# Function: table\_getExpandedRowModel() + +```ts +function table_getExpandedRowModel(table): RowModel; +``` + +Defined in: [core/row-models/coreRowModelsFeature.utils.ts:186](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.utils.ts#L186) + +Returns expanded row model for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`RowModel`](../../index/interfaces/RowModel.md)\<`TFeatures`, `TData`\> + +## Example + +```ts +const value = table_getExpandedRowModel(table) +``` diff --git a/docs/reference/static-functions/functions/table_getFilteredRowModel.md b/docs/reference/static-functions/functions/table_getFilteredRowModel.md new file mode 100644 index 0000000000..a9dedd8526 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getFilteredRowModel.md @@ -0,0 +1,42 @@ +--- +id: table_getFilteredRowModel +title: table_getFilteredRowModel +--- + +# Function: table\_getFilteredRowModel() + +```ts +function table_getFilteredRowModel(table): RowModel; +``` + +Defined in: [core/row-models/coreRowModelsFeature.utils.ts:57](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.utils.ts#L57) + +Returns filtered row model for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`RowModel`](../../index/interfaces/RowModel.md)\<`TFeatures`, `TData`\> + +## Example + +```ts +const value = table_getFilteredRowModel(table) +``` diff --git a/docs/reference/static-functions/functions/table_getFilteredSelectedRowModel.md b/docs/reference/static-functions/functions/table_getFilteredSelectedRowModel.md new file mode 100644 index 0000000000..15717920e9 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getFilteredSelectedRowModel.md @@ -0,0 +1,42 @@ +--- +id: table_getFilteredSelectedRowModel +title: table_getFilteredSelectedRowModel +--- + +# Function: table\_getFilteredSelectedRowModel() + +```ts +function table_getFilteredSelectedRowModel(table): RowModel; +``` + +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:193](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L193) + +Returns filtered selected row model for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`RowModel`](../../index/interfaces/RowModel.md)\<`TFeatures`, `TData`\> + +## Example + +```ts +const value = table_getFilteredSelectedRowModel(table) +``` diff --git a/docs/reference/static-functions/functions/table_getFlatHeaders.md b/docs/reference/static-functions/functions/table_getFlatHeaders.md new file mode 100644 index 0000000000..6b51395f50 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getFlatHeaders.md @@ -0,0 +1,42 @@ +--- +id: table_getFlatHeaders +title: table_getFlatHeaders +--- + +# Function: table\_getFlatHeaders() + +```ts +function table_getFlatHeaders(table): Header[]; +``` + +Defined in: [core/headers/coreHeadersFeature.utils.ts:140](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.utils.ts#L140) + +Returns flat headers for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`Header`](../../index/type-aliases/Header.md)\<`TFeatures`, `TData`, `unknown`\>[] + +## Example + +```ts +const value = table_getFlatHeaders(table) +``` diff --git a/docs/reference/static-functions/functions/table_getFooterGroups.md b/docs/reference/static-functions/functions/table_getFooterGroups.md new file mode 100644 index 0000000000..917eb1588d --- /dev/null +++ b/docs/reference/static-functions/functions/table_getFooterGroups.md @@ -0,0 +1,42 @@ +--- +id: table_getFooterGroups +title: table_getFooterGroups +--- + +# Function: table\_getFooterGroups() + +```ts +function table_getFooterGroups(table): HeaderGroup[]; +``` + +Defined in: [core/headers/coreHeadersFeature.utils.ts:122](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.utils.ts#L122) + +Returns footer groups for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`HeaderGroup`](../../index/type-aliases/HeaderGroup.md)\<`TFeatures`, `TData`\>[] + +## Example + +```ts +const value = table_getFooterGroups(table) +``` diff --git a/docs/reference/static-functions/functions/table_getGlobalAutoFilterFn.md b/docs/reference/static-functions/functions/table_getGlobalAutoFilterFn.md new file mode 100644 index 0000000000..3cfe7619cd --- /dev/null +++ b/docs/reference/static-functions/functions/table_getGlobalAutoFilterFn.md @@ -0,0 +1,26 @@ +--- +id: table_getGlobalAutoFilterFn +title: table_getGlobalAutoFilterFn +--- + +# Function: table\_getGlobalAutoFilterFn() + +```ts +function table_getGlobalAutoFilterFn(): FilterFn; +``` + +Defined in: [features/global-filtering/globalFilteringFeature.utils.ts:43](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.utils.ts#L43) + +Returns global auto filter fn for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Returns + +[`FilterFn`](../../index/interfaces/FilterFn.md)\<`any`, `any`\> + +## Example + +```ts +const value = table_getGlobalAutoFilterFn(table) +``` diff --git a/docs/reference/static-functions/functions/table_getGlobalFacetedMinMaxValues.md b/docs/reference/static-functions/functions/table_getGlobalFacetedMinMaxValues.md new file mode 100644 index 0000000000..e0d7277dba --- /dev/null +++ b/docs/reference/static-functions/functions/table_getGlobalFacetedMinMaxValues.md @@ -0,0 +1,42 @@ +--- +id: table_getGlobalFacetedMinMaxValues +title: table_getGlobalFacetedMinMaxValues +--- + +# Function: table\_getGlobalFacetedMinMaxValues() + +```ts +function table_getGlobalFacetedMinMaxValues(table): [number, number] | undefined; +``` + +Defined in: [features/column-faceting/columnFacetingFeature.utils.ts:89](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.utils.ts#L89) + +Returns global faceted min max values for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +\[`number`, `number`\] \| `undefined` + +## Example + +```ts +const value = table_getGlobalFacetedMinMaxValues(table) +``` diff --git a/docs/reference/static-functions/functions/table_getGlobalFacetedRowModel.md b/docs/reference/static-functions/functions/table_getGlobalFacetedRowModel.md new file mode 100644 index 0000000000..4490a71a04 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getGlobalFacetedRowModel.md @@ -0,0 +1,42 @@ +--- +id: table_getGlobalFacetedRowModel +title: table_getGlobalFacetedRowModel +--- + +# Function: table\_getGlobalFacetedRowModel() + +```ts +function table_getGlobalFacetedRowModel(table): RowModel; +``` + +Defined in: [features/column-faceting/columnFacetingFeature.utils.ts:109](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.utils.ts#L109) + +Returns global faceted row model for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`RowModel`](../../index/interfaces/RowModel.md)\<`TFeatures`, `TData`\> + +## Example + +```ts +const value = table_getGlobalFacetedRowModel(table) +``` diff --git a/docs/reference/static-functions/functions/table_getGlobalFacetedUniqueValues.md b/docs/reference/static-functions/functions/table_getGlobalFacetedUniqueValues.md new file mode 100644 index 0000000000..73aeef11c1 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getGlobalFacetedUniqueValues.md @@ -0,0 +1,42 @@ +--- +id: table_getGlobalFacetedUniqueValues +title: table_getGlobalFacetedUniqueValues +--- + +# Function: table\_getGlobalFacetedUniqueValues() + +```ts +function table_getGlobalFacetedUniqueValues(table): Map; +``` + +Defined in: [features/column-faceting/columnFacetingFeature.utils.ts:129](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-faceting/columnFacetingFeature.utils.ts#L129) + +Returns global faceted unique values for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`Map`\<`any`, `number`\> + +## Example + +```ts +const value = table_getGlobalFacetedUniqueValues(table) +``` diff --git a/docs/reference/static-functions/functions/table_getGlobalFilterFn.md b/docs/reference/static-functions/functions/table_getGlobalFilterFn.md new file mode 100644 index 0000000000..ef46b6d0d7 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getGlobalFilterFn.md @@ -0,0 +1,45 @@ +--- +id: table_getGlobalFilterFn +title: table_getGlobalFilterFn +--- + +# Function: table\_getGlobalFilterFn() + +```ts +function table_getGlobalFilterFn(table): + | FilterFn + | undefined; +``` + +Defined in: [features/global-filtering/globalFilteringFeature.utils.ts:57](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.utils.ts#L57) + +Returns global filter fn for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + + \| [`FilterFn`](../../index/interfaces/FilterFn.md)\<`TFeatures`, `TData`\> + \| `undefined` + +## Example + +```ts +const value = table_getGlobalFilterFn(table) +``` diff --git a/docs/reference/static-functions/functions/table_getGroupedRowModel.md b/docs/reference/static-functions/functions/table_getGroupedRowModel.md new file mode 100644 index 0000000000..f42d4af58d --- /dev/null +++ b/docs/reference/static-functions/functions/table_getGroupedRowModel.md @@ -0,0 +1,42 @@ +--- +id: table_getGroupedRowModel +title: table_getGroupedRowModel +--- + +# Function: table\_getGroupedRowModel() + +```ts +function table_getGroupedRowModel(table): RowModel; +``` + +Defined in: [core/row-models/coreRowModelsFeature.utils.ts:100](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.utils.ts#L100) + +Returns grouped row model for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`RowModel`](../../index/interfaces/RowModel.md)\<`TFeatures`, `TData`\> + +## Example + +```ts +const value = table_getGroupedRowModel(table) +``` diff --git a/docs/reference/static-functions/functions/table_getGroupedSelectedRowModel.md b/docs/reference/static-functions/functions/table_getGroupedSelectedRowModel.md new file mode 100644 index 0000000000..14c1ae7fe6 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getGroupedSelectedRowModel.md @@ -0,0 +1,42 @@ +--- +id: table_getGroupedSelectedRowModel +title: table_getGroupedSelectedRowModel +--- + +# Function: table\_getGroupedSelectedRowModel() + +```ts +function table_getGroupedSelectedRowModel(table): RowModel; +``` + +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:220](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L220) + +Returns grouped selected row model for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`RowModel`](../../index/interfaces/RowModel.md)\<`TFeatures`, `TData`\> + +## Example + +```ts +const value = table_getGroupedSelectedRowModel(table) +``` diff --git a/docs/reference/static-functions/functions/table_getHeaderGroups.md b/docs/reference/static-functions/functions/table_getHeaderGroups.md new file mode 100644 index 0000000000..1ec3e9d8b4 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getHeaderGroups.md @@ -0,0 +1,42 @@ +--- +id: table_getHeaderGroups +title: table_getHeaderGroups +--- + +# Function: table\_getHeaderGroups() + +```ts +function table_getHeaderGroups(table): HeaderGroup[]; +``` + +Defined in: [core/headers/coreHeadersFeature.utils.ts:78](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.utils.ts#L78) + +Returns header groups for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`HeaderGroup`](../../index/type-aliases/HeaderGroup.md)\<`TFeatures`, `TData`\>[] + +## Example + +```ts +const value = table_getHeaderGroups(table) +``` diff --git a/docs/reference/static-functions/functions/table_getIsAllColumnsVisible.md b/docs/reference/static-functions/functions/table_getIsAllColumnsVisible.md new file mode 100644 index 0000000000..b573490860 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getIsAllColumnsVisible.md @@ -0,0 +1,42 @@ +--- +id: table_getIsAllColumnsVisible +title: table_getIsAllColumnsVisible +--- + +# Function: table\_getIsAllColumnsVisible() + +```ts +function table_getIsAllColumnsVisible(table): boolean; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.utils.ts:281](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.utils.ts#L281) + +Returns is all columns visible for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = table_getIsAllColumnsVisible(table) +``` diff --git a/docs/reference/static-functions/functions/table_getIsAllPageRowsSelected.md b/docs/reference/static-functions/functions/table_getIsAllPageRowsSelected.md new file mode 100644 index 0000000000..900f802da8 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getIsAllPageRowsSelected.md @@ -0,0 +1,42 @@ +--- +id: table_getIsAllPageRowsSelected +title: table_getIsAllPageRowsSelected +--- + +# Function: table\_getIsAllPageRowsSelected() + +```ts +function table_getIsAllPageRowsSelected(table): boolean; +``` + +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:281](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L281) + +Returns is all page rows selected for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = table_getIsAllPageRowsSelected(table) +``` diff --git a/docs/reference/static-functions/functions/table_getIsAllRowsExpanded.md b/docs/reference/static-functions/functions/table_getIsAllRowsExpanded.md new file mode 100644 index 0000000000..35713da049 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getIsAllRowsExpanded.md @@ -0,0 +1,42 @@ +--- +id: table_getIsAllRowsExpanded +title: table_getIsAllRowsExpanded +--- + +# Function: table\_getIsAllRowsExpanded() + +```ts +function table_getIsAllRowsExpanded(table): boolean; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:174](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L174) + +Returns is all rows expanded for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = table_getIsAllRowsExpanded(table) +``` diff --git a/docs/reference/static-functions/functions/table_getIsAllRowsSelected.md b/docs/reference/static-functions/functions/table_getIsAllRowsSelected.md new file mode 100644 index 0000000000..91655c0ac1 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getIsAllRowsSelected.md @@ -0,0 +1,42 @@ +--- +id: table_getIsAllRowsSelected +title: table_getIsAllRowsSelected +--- + +# Function: table\_getIsAllRowsSelected() + +```ts +function table_getIsAllRowsSelected(table): boolean; +``` + +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:247](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L247) + +Returns is all rows selected for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = table_getIsAllRowsSelected(table) +``` diff --git a/docs/reference/static-functions/functions/table_getIsSomeColumnsPinned.md b/docs/reference/static-functions/functions/table_getIsSomeColumnsPinned.md new file mode 100644 index 0000000000..e0750abc6d --- /dev/null +++ b/docs/reference/static-functions/functions/table_getIsSomeColumnsPinned.md @@ -0,0 +1,46 @@ +--- +id: table_getIsSomeColumnsPinned +title: table_getIsSomeColumnsPinned +--- + +# Function: table\_getIsSomeColumnsPinned() + +```ts +function table_getIsSomeColumnsPinned(table, position?): boolean; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:312](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L312) + +Returns is some columns pinned for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### position? + +[`ColumnPinningPosition`](../../index/type-aliases/ColumnPinningPosition.md) + +## Returns + +`boolean` + +## Example + +```ts +const value = table_getIsSomeColumnsPinned(table) +``` diff --git a/docs/reference/static-functions/functions/table_getIsSomeColumnsVisible.md b/docs/reference/static-functions/functions/table_getIsSomeColumnsVisible.md new file mode 100644 index 0000000000..f0fd137456 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getIsSomeColumnsVisible.md @@ -0,0 +1,42 @@ +--- +id: table_getIsSomeColumnsVisible +title: table_getIsSomeColumnsVisible +--- + +# Function: table\_getIsSomeColumnsVisible() + +```ts +function table_getIsSomeColumnsVisible(table): boolean; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.utils.ts:303](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.utils.ts#L303) + +Returns is some columns visible for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = table_getIsSomeColumnsVisible(table) +``` diff --git a/docs/reference/static-functions/functions/table_getIsSomePageRowsSelected.md b/docs/reference/static-functions/functions/table_getIsSomePageRowsSelected.md new file mode 100644 index 0000000000..ea0f80a8ef --- /dev/null +++ b/docs/reference/static-functions/functions/table_getIsSomePageRowsSelected.md @@ -0,0 +1,42 @@ +--- +id: table_getIsSomePageRowsSelected +title: table_getIsSomePageRowsSelected +--- + +# Function: table\_getIsSomePageRowsSelected() + +```ts +function table_getIsSomePageRowsSelected(table): boolean; +``` + +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:335](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L335) + +Returns is some page rows selected for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = table_getIsSomePageRowsSelected(table) +``` diff --git a/docs/reference/static-functions/functions/table_getIsSomeRowsExpanded.md b/docs/reference/static-functions/functions/table_getIsSomeRowsExpanded.md new file mode 100644 index 0000000000..dd574b7f6e --- /dev/null +++ b/docs/reference/static-functions/functions/table_getIsSomeRowsExpanded.md @@ -0,0 +1,42 @@ +--- +id: table_getIsSomeRowsExpanded +title: table_getIsSomeRowsExpanded +--- + +# Function: table\_getIsSomeRowsExpanded() + +```ts +function table_getIsSomeRowsExpanded(table): boolean; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:156](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L156) + +Returns is some rows expanded for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = table_getIsSomeRowsExpanded(table) +``` diff --git a/docs/reference/static-functions/functions/table_getIsSomeRowsPinned.md b/docs/reference/static-functions/functions/table_getIsSomeRowsPinned.md new file mode 100644 index 0000000000..6c35932187 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getIsSomeRowsPinned.md @@ -0,0 +1,46 @@ +--- +id: table_getIsSomeRowsPinned +title: table_getIsSomeRowsPinned +--- + +# Function: table\_getIsSomeRowsPinned() + +```ts +function table_getIsSomeRowsPinned(table, position?): boolean; +``` + +Defined in: [features/row-pinning/rowPinningFeature.utils.ts:88](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.utils.ts#L88) + +Returns is some rows pinned for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### position? + +[`RowPinningPosition`](../../index/type-aliases/RowPinningPosition.md) + +## Returns + +`boolean` + +## Example + +```ts +const value = table_getIsSomeRowsPinned(table) +``` diff --git a/docs/reference/static-functions/functions/table_getIsSomeRowsSelected.md b/docs/reference/static-functions/functions/table_getIsSomeRowsSelected.md new file mode 100644 index 0000000000..e7236f1f8a --- /dev/null +++ b/docs/reference/static-functions/functions/table_getIsSomeRowsSelected.md @@ -0,0 +1,42 @@ +--- +id: table_getIsSomeRowsSelected +title: table_getIsSomeRowsSelected +--- + +# Function: table\_getIsSomeRowsSelected() + +```ts +function table_getIsSomeRowsSelected(table): boolean; +``` + +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:312](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L312) + +Returns is some rows selected for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`boolean` + +## Example + +```ts +const value = table_getIsSomeRowsSelected(table) +``` diff --git a/docs/reference/static-functions/functions/table_getLeafHeaders.md b/docs/reference/static-functions/functions/table_getLeafHeaders.md new file mode 100644 index 0000000000..8b9a9459c0 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getLeafHeaders.md @@ -0,0 +1,42 @@ +--- +id: table_getLeafHeaders +title: table_getLeafHeaders +--- + +# Function: table\_getLeafHeaders() + +```ts +function table_getLeafHeaders(table): any[]; +``` + +Defined in: [core/headers/coreHeadersFeature.utils.ts:162](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/headers/coreHeadersFeature.utils.ts#L162) + +Returns leaf headers for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`any`[] + +## Example + +```ts +const value = table_getLeafHeaders(table) +``` diff --git a/docs/reference/static-functions/functions/table_getLeftFlatHeaders.md b/docs/reference/static-functions/functions/table_getLeftFlatHeaders.md new file mode 100644 index 0000000000..453f2eb79b --- /dev/null +++ b/docs/reference/static-functions/functions/table_getLeftFlatHeaders.md @@ -0,0 +1,42 @@ +--- +id: table_getLeftFlatHeaders +title: table_getLeftFlatHeaders +--- + +# Function: table\_getLeftFlatHeaders() + +```ts +function table_getLeftFlatHeaders(table): any[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:498](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L498) + +Returns left flat headers for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`any`[] + +## Example + +```ts +const value = table_getLeftFlatHeaders(table) +``` diff --git a/docs/reference/static-functions/functions/table_getLeftFooterGroups.md b/docs/reference/static-functions/functions/table_getLeftFooterGroups.md new file mode 100644 index 0000000000..16a6962b03 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getLeftFooterGroups.md @@ -0,0 +1,42 @@ +--- +id: table_getLeftFooterGroups +title: table_getLeftFooterGroups +--- + +# Function: table\_getLeftFooterGroups() + +```ts +function table_getLeftFooterGroups(table): any[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:430](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L430) + +Returns left footer groups for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`any`[] + +## Example + +```ts +const value = table_getLeftFooterGroups(table) +``` diff --git a/docs/reference/static-functions/functions/table_getLeftHeaderGroups.md b/docs/reference/static-functions/functions/table_getLeftHeaderGroups.md new file mode 100644 index 0000000000..d7a2dd40f7 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getLeftHeaderGroups.md @@ -0,0 +1,42 @@ +--- +id: table_getLeftHeaderGroups +title: table_getLeftHeaderGroups +--- + +# Function: table\_getLeftHeaderGroups() + +```ts +function table_getLeftHeaderGroups(table): HeaderGroup[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:336](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L336) + +Returns left header groups for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`HeaderGroup`](../../index/type-aliases/HeaderGroup.md)\<`TFeatures`, `TData`\>[] + +## Example + +```ts +const value = table_getLeftHeaderGroups(table) +``` diff --git a/docs/reference/static-functions/functions/table_getLeftLeafColumns.md b/docs/reference/static-functions/functions/table_getLeftLeafColumns.md new file mode 100644 index 0000000000..6dd678668c --- /dev/null +++ b/docs/reference/static-functions/functions/table_getLeftLeafColumns.md @@ -0,0 +1,42 @@ +--- +id: table_getLeftLeafColumns +title: table_getLeftLeafColumns +--- + +# Function: table\_getLeftLeafColumns() + +```ts +function table_getLeftLeafColumns(table): Column[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:643](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L643) + +Returns left leaf columns for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`Column`](../../index/type-aliases/Column.md)\<`TFeatures`, `TData`, `unknown`\>[] + +## Example + +```ts +const value = table_getLeftLeafColumns(table) +``` diff --git a/docs/reference/static-functions/functions/table_getLeftLeafHeaders.md b/docs/reference/static-functions/functions/table_getLeftLeafHeaders.md new file mode 100644 index 0000000000..fba5a5f2c8 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getLeftLeafHeaders.md @@ -0,0 +1,42 @@ +--- +id: table_getLeftLeafHeaders +title: table_getLeftLeafHeaders +--- + +# Function: table\_getLeftLeafHeaders() + +```ts +function table_getLeftLeafHeaders(table): any[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:578](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L578) + +Returns left leaf headers for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`any`[] + +## Example + +```ts +const value = table_getLeftLeafHeaders(table) +``` diff --git a/docs/reference/static-functions/functions/table_getLeftTotalSize.md b/docs/reference/static-functions/functions/table_getLeftTotalSize.md new file mode 100644 index 0000000000..5edf1d49fe --- /dev/null +++ b/docs/reference/static-functions/functions/table_getLeftTotalSize.md @@ -0,0 +1,42 @@ +--- +id: table_getLeftTotalSize +title: table_getLeftTotalSize +--- + +# Function: table\_getLeftTotalSize() + +```ts +function table_getLeftTotalSize(table): any; +``` + +Defined in: [features/column-sizing/columnSizingFeature.utils.ts:289](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.utils.ts#L289) + +Returns left total size for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`any` + +## Example + +```ts +const value = table_getLeftTotalSize(table) +``` diff --git a/docs/reference/static-functions/functions/table_getLeftVisibleLeafColumns.md b/docs/reference/static-functions/functions/table_getLeftVisibleLeafColumns.md new file mode 100644 index 0000000000..062239ac20 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getLeftVisibleLeafColumns.md @@ -0,0 +1,42 @@ +--- +id: table_getLeftVisibleLeafColumns +title: table_getLeftVisibleLeafColumns +--- + +# Function: table\_getLeftVisibleLeafColumns() + +```ts +function table_getLeftVisibleLeafColumns(table): any[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:751](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L751) + +Returns left visible leaf columns for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`any`[] + +## Example + +```ts +const value = table_getLeftVisibleLeafColumns(table) +``` diff --git a/docs/reference/static-functions/functions/table_getOrderColumnsFn.md b/docs/reference/static-functions/functions/table_getOrderColumnsFn.md new file mode 100644 index 0000000000..940b0150cc --- /dev/null +++ b/docs/reference/static-functions/functions/table_getOrderColumnsFn.md @@ -0,0 +1,54 @@ +--- +id: table_getOrderColumnsFn +title: table_getOrderColumnsFn +--- + +# Function: table\_getOrderColumnsFn() + +```ts +function table_getOrderColumnsFn(table): (columns) => Column_Internal[]; +``` + +Defined in: [features/column-ordering/columnOrderingFeature.utils.ts:139](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-ordering/columnOrderingFeature.utils.ts#L139) + +Returns order columns fn for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +```ts +(columns): Column_Internal[]; +``` + +### Parameters + +#### columns + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `unknown`\>[] + +### Returns + +[`Column_Internal`](../../index/type-aliases/Column_Internal.md)\<`TFeatures`, `TData`, `unknown`\>[] + +## Example + +```ts +const value = table_getOrderColumnsFn(table) +``` diff --git a/docs/reference/static-functions/functions/table_getPageCount.md b/docs/reference/static-functions/functions/table_getPageCount.md new file mode 100644 index 0000000000..aea62aaa1e --- /dev/null +++ b/docs/reference/static-functions/functions/table_getPageCount.md @@ -0,0 +1,42 @@ +--- +id: table_getPageCount +title: table_getPageCount +--- + +# Function: table\_getPageCount() + +```ts +function table_getPageCount(table): number; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.utils.ts:355](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.utils.ts#L355) + +Returns page count for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`number` + +## Example + +```ts +const value = table_getPageCount(table) +``` diff --git a/docs/reference/static-functions/functions/table_getPageOptions.md b/docs/reference/static-functions/functions/table_getPageOptions.md new file mode 100644 index 0000000000..a08bb6d5b0 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getPageOptions.md @@ -0,0 +1,42 @@ +--- +id: table_getPageOptions +title: table_getPageOptions +--- + +# Function: table\_getPageOptions() + +```ts +function table_getPageOptions(table): number[]; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.utils.ts:215](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.utils.ts#L215) + +Returns page options for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`number`[] + +## Example + +```ts +const value = table_getPageOptions(table) +``` diff --git a/docs/reference/static-functions/functions/table_getPaginatedRowModel.md b/docs/reference/static-functions/functions/table_getPaginatedRowModel.md new file mode 100644 index 0000000000..d3cdb0e5b0 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getPaginatedRowModel.md @@ -0,0 +1,42 @@ +--- +id: table_getPaginatedRowModel +title: table_getPaginatedRowModel +--- + +# Function: table\_getPaginatedRowModel() + +```ts +function table_getPaginatedRowModel(table): RowModel; +``` + +Defined in: [core/row-models/coreRowModelsFeature.utils.ts:229](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.utils.ts#L229) + +Returns paginated row model for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`RowModel`](../../index/interfaces/RowModel.md)\<`TFeatures`, `TData`\> + +## Example + +```ts +const value = table_getPaginatedRowModel(table) +``` diff --git a/docs/reference/static-functions/functions/table_getPinnedLeafColumns.md b/docs/reference/static-functions/functions/table_getPinnedLeafColumns.md new file mode 100644 index 0000000000..b2a76320e9 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getPinnedLeafColumns.md @@ -0,0 +1,46 @@ +--- +id: table_getPinnedLeafColumns +title: table_getPinnedLeafColumns +--- + +# Function: table\_getPinnedLeafColumns() + +```ts +function table_getPinnedLeafColumns(table, position): any[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:711](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L711) + +Returns pinned leaf columns for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### position + +[`ColumnPinningPosition`](../../index/type-aliases/ColumnPinningPosition.md) | `"center"` + +## Returns + +`any`[] + +## Example + +```ts +const value = table_getPinnedLeafColumns(table) +``` diff --git a/docs/reference/static-functions/functions/table_getPinnedVisibleLeafColumns.md b/docs/reference/static-functions/functions/table_getPinnedVisibleLeafColumns.md new file mode 100644 index 0000000000..2b5642144e --- /dev/null +++ b/docs/reference/static-functions/functions/table_getPinnedVisibleLeafColumns.md @@ -0,0 +1,46 @@ +--- +id: table_getPinnedVisibleLeafColumns +title: table_getPinnedVisibleLeafColumns +--- + +# Function: table\_getPinnedVisibleLeafColumns() + +```ts +function table_getPinnedVisibleLeafColumns(table, position?): any[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:820](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L820) + +Returns pinned visible leaf columns for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### position? + +[`ColumnPinningPosition`](../../index/type-aliases/ColumnPinningPosition.md) | `"center"` + +## Returns + +`any`[] + +## Example + +```ts +const value = table_getPinnedVisibleLeafColumns(table) +``` diff --git a/docs/reference/static-functions/functions/table_getPreExpandedRowModel.md b/docs/reference/static-functions/functions/table_getPreExpandedRowModel.md new file mode 100644 index 0000000000..aed1349715 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getPreExpandedRowModel.md @@ -0,0 +1,42 @@ +--- +id: table_getPreExpandedRowModel +title: table_getPreExpandedRowModel +--- + +# Function: table\_getPreExpandedRowModel() + +```ts +function table_getPreExpandedRowModel(table): RowModel; +``` + +Defined in: [core/row-models/coreRowModelsFeature.utils.ts:169](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.utils.ts#L169) + +Returns pre expanded row model for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`RowModel`](../../index/interfaces/RowModel.md)\<`TFeatures`, `TData`\> + +## Example + +```ts +const value = table_getPreExpandedRowModel(table) +``` diff --git a/docs/reference/static-functions/functions/table_getPreFilteredRowModel.md b/docs/reference/static-functions/functions/table_getPreFilteredRowModel.md new file mode 100644 index 0000000000..545f071b45 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getPreFilteredRowModel.md @@ -0,0 +1,42 @@ +--- +id: table_getPreFilteredRowModel +title: table_getPreFilteredRowModel +--- + +# Function: table\_getPreFilteredRowModel() + +```ts +function table_getPreFilteredRowModel(table): RowModel; +``` + +Defined in: [core/row-models/coreRowModelsFeature.utils.ts:40](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.utils.ts#L40) + +Returns pre filtered row model for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`RowModel`](../../index/interfaces/RowModel.md)\<`TFeatures`, `TData`\> + +## Example + +```ts +const value = table_getPreFilteredRowModel(table) +``` diff --git a/docs/reference/static-functions/functions/table_getPreGroupedRowModel.md b/docs/reference/static-functions/functions/table_getPreGroupedRowModel.md new file mode 100644 index 0000000000..cced5587be --- /dev/null +++ b/docs/reference/static-functions/functions/table_getPreGroupedRowModel.md @@ -0,0 +1,42 @@ +--- +id: table_getPreGroupedRowModel +title: table_getPreGroupedRowModel +--- + +# Function: table\_getPreGroupedRowModel() + +```ts +function table_getPreGroupedRowModel(table): RowModel; +``` + +Defined in: [core/row-models/coreRowModelsFeature.utils.ts:83](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.utils.ts#L83) + +Returns pre grouped row model for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`RowModel`](../../index/interfaces/RowModel.md)\<`TFeatures`, `TData`\> + +## Example + +```ts +const value = table_getPreGroupedRowModel(table) +``` diff --git a/docs/reference/static-functions/functions/table_getPrePaginatedRowModel.md b/docs/reference/static-functions/functions/table_getPrePaginatedRowModel.md new file mode 100644 index 0000000000..906b368b48 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getPrePaginatedRowModel.md @@ -0,0 +1,42 @@ +--- +id: table_getPrePaginatedRowModel +title: table_getPrePaginatedRowModel +--- + +# Function: table\_getPrePaginatedRowModel() + +```ts +function table_getPrePaginatedRowModel(table): RowModel; +``` + +Defined in: [core/row-models/coreRowModelsFeature.utils.ts:212](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.utils.ts#L212) + +Returns pre paginated row model for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`RowModel`](../../index/interfaces/RowModel.md)\<`TFeatures`, `TData`\> + +## Example + +```ts +const value = table_getPrePaginatedRowModel(table) +``` diff --git a/docs/reference/static-functions/functions/table_getPreSelectedRowModel.md b/docs/reference/static-functions/functions/table_getPreSelectedRowModel.md new file mode 100644 index 0000000000..d2ed17fbaa --- /dev/null +++ b/docs/reference/static-functions/functions/table_getPreSelectedRowModel.md @@ -0,0 +1,42 @@ +--- +id: table_getPreSelectedRowModel +title: table_getPreSelectedRowModel +--- + +# Function: table\_getPreSelectedRowModel() + +```ts +function table_getPreSelectedRowModel(table): RowModel; +``` + +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:149](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L149) + +Returns pre selected row model for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`RowModel`](../../index/interfaces/RowModel.md)\<`TFeatures`, `TData`\> + +## Example + +```ts +const value = table_getPreSelectedRowModel(table) +``` diff --git a/docs/reference/static-functions/functions/table_getPreSortedRowModel.md b/docs/reference/static-functions/functions/table_getPreSortedRowModel.md new file mode 100644 index 0000000000..a4727b7774 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getPreSortedRowModel.md @@ -0,0 +1,42 @@ +--- +id: table_getPreSortedRowModel +title: table_getPreSortedRowModel +--- + +# Function: table\_getPreSortedRowModel() + +```ts +function table_getPreSortedRowModel(table): RowModel; +``` + +Defined in: [core/row-models/coreRowModelsFeature.utils.ts:126](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.utils.ts#L126) + +Returns pre sorted row model for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`RowModel`](../../index/interfaces/RowModel.md)\<`TFeatures`, `TData`\> + +## Example + +```ts +const value = table_getPreSortedRowModel(table) +``` diff --git a/docs/reference/static-functions/functions/table_getRightFlatHeaders.md b/docs/reference/static-functions/functions/table_getRightFlatHeaders.md new file mode 100644 index 0000000000..876c74ce49 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getRightFlatHeaders.md @@ -0,0 +1,42 @@ +--- +id: table_getRightFlatHeaders +title: table_getRightFlatHeaders +--- + +# Function: table\_getRightFlatHeaders() + +```ts +function table_getRightFlatHeaders(table): any[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:524](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L524) + +Returns right flat headers for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`any`[] + +## Example + +```ts +const value = table_getRightFlatHeaders(table) +``` diff --git a/docs/reference/static-functions/functions/table_getRightFooterGroups.md b/docs/reference/static-functions/functions/table_getRightFooterGroups.md new file mode 100644 index 0000000000..9f4a1f92e6 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getRightFooterGroups.md @@ -0,0 +1,42 @@ +--- +id: table_getRightFooterGroups +title: table_getRightFooterGroups +--- + +# Function: table\_getRightFooterGroups() + +```ts +function table_getRightFooterGroups(table): any[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:452](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L452) + +Returns right footer groups for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`any`[] + +## Example + +```ts +const value = table_getRightFooterGroups(table) +``` diff --git a/docs/reference/static-functions/functions/table_getRightHeaderGroups.md b/docs/reference/static-functions/functions/table_getRightHeaderGroups.md new file mode 100644 index 0000000000..71c4d3f66b --- /dev/null +++ b/docs/reference/static-functions/functions/table_getRightHeaderGroups.md @@ -0,0 +1,42 @@ +--- +id: table_getRightHeaderGroups +title: table_getRightHeaderGroups +--- + +# Function: table\_getRightHeaderGroups() + +```ts +function table_getRightHeaderGroups(table): HeaderGroup[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:366](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L366) + +Returns right header groups for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`HeaderGroup`](../../index/type-aliases/HeaderGroup.md)\<`TFeatures`, `TData`\>[] + +## Example + +```ts +const value = table_getRightHeaderGroups(table) +``` diff --git a/docs/reference/static-functions/functions/table_getRightLeafColumns.md b/docs/reference/static-functions/functions/table_getRightLeafColumns.md new file mode 100644 index 0000000000..9afbca3933 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getRightLeafColumns.md @@ -0,0 +1,42 @@ +--- +id: table_getRightLeafColumns +title: table_getRightLeafColumns +--- + +# Function: table\_getRightLeafColumns() + +```ts +function table_getRightLeafColumns(table): Column[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:667](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L667) + +Returns right leaf columns for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`Column`](../../index/type-aliases/Column.md)\<`TFeatures`, `TData`, `unknown`\>[] + +## Example + +```ts +const value = table_getRightLeafColumns(table) +``` diff --git a/docs/reference/static-functions/functions/table_getRightLeafHeaders.md b/docs/reference/static-functions/functions/table_getRightLeafHeaders.md new file mode 100644 index 0000000000..57ce2b70b0 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getRightLeafHeaders.md @@ -0,0 +1,42 @@ +--- +id: table_getRightLeafHeaders +title: table_getRightLeafHeaders +--- + +# Function: table\_getRightLeafHeaders() + +```ts +function table_getRightLeafHeaders(table): any[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:599](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L599) + +Returns right leaf headers for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`any`[] + +## Example + +```ts +const value = table_getRightLeafHeaders(table) +``` diff --git a/docs/reference/static-functions/functions/table_getRightTotalSize.md b/docs/reference/static-functions/functions/table_getRightTotalSize.md new file mode 100644 index 0000000000..49e5ac81dd --- /dev/null +++ b/docs/reference/static-functions/functions/table_getRightTotalSize.md @@ -0,0 +1,42 @@ +--- +id: table_getRightTotalSize +title: table_getRightTotalSize +--- + +# Function: table\_getRightTotalSize() + +```ts +function table_getRightTotalSize(table): any; +``` + +Defined in: [features/column-sizing/columnSizingFeature.utils.ts:339](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.utils.ts#L339) + +Returns right total size for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`any` + +## Example + +```ts +const value = table_getRightTotalSize(table) +``` diff --git a/docs/reference/static-functions/functions/table_getRightVisibleLeafColumns.md b/docs/reference/static-functions/functions/table_getRightVisibleLeafColumns.md new file mode 100644 index 0000000000..1f2b1a308f --- /dev/null +++ b/docs/reference/static-functions/functions/table_getRightVisibleLeafColumns.md @@ -0,0 +1,42 @@ +--- +id: table_getRightVisibleLeafColumns +title: table_getRightVisibleLeafColumns +--- + +# Function: table\_getRightVisibleLeafColumns() + +```ts +function table_getRightVisibleLeafColumns(table): any[]; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:774](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L774) + +Returns right visible leaf columns for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`any`[] + +## Example + +```ts +const value = table_getRightVisibleLeafColumns(table) +``` diff --git a/docs/reference/static-functions/functions/table_getRow.md b/docs/reference/static-functions/functions/table_getRow.md new file mode 100644 index 0000000000..16c197bc1f --- /dev/null +++ b/docs/reference/static-functions/functions/table_getRow.md @@ -0,0 +1,53 @@ +--- +id: table_getRow +title: table_getRow +--- + +# Function: table\_getRow() + +```ts +function table_getRow( + table, + rowId, +searchAll?): Row; +``` + +Defined in: [core/rows/coreRowsFeature.utils.ts:230](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.utils.ts#L230) + +Returns row for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### rowId + +`string` + +### searchAll? + +`boolean` + +## Returns + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +## Example + +```ts +const value = table_getRow(table) +``` diff --git a/docs/reference/static-functions/functions/table_getRowCount.md b/docs/reference/static-functions/functions/table_getRowCount.md new file mode 100644 index 0000000000..c82c23ef79 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getRowCount.md @@ -0,0 +1,42 @@ +--- +id: table_getRowCount +title: table_getRowCount +--- + +# Function: table\_getRowCount() + +```ts +function table_getRowCount(table): number; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.utils.ts:378](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.utils.ts#L378) + +Returns row count for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`number` + +## Example + +```ts +const value = table_getRowCount(table) +``` diff --git a/docs/reference/static-functions/functions/table_getRowId.md b/docs/reference/static-functions/functions/table_getRowId.md new file mode 100644 index 0000000000..b9b9738be8 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getRowId.md @@ -0,0 +1,58 @@ +--- +id: table_getRowId +title: table_getRowId +--- + +# Function: table\_getRowId() + +```ts +function table_getRowId( + originalRow, + table, + index, + parent?): string; +``` + +Defined in: [core/rows/coreRowsFeature.utils.ts:205](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/rows/coreRowsFeature.utils.ts#L205) + +Returns row id for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### originalRow + +`TData` + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### index + +`number` + +### parent? + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\> + +## Returns + +`string` + +## Example + +```ts +const value = table_getRowId(table) +``` diff --git a/docs/reference/static-functions/functions/table_getRowModel.md b/docs/reference/static-functions/functions/table_getRowModel.md new file mode 100644 index 0000000000..c4a0ebfef5 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getRowModel.md @@ -0,0 +1,42 @@ +--- +id: table_getRowModel +title: table_getRowModel +--- + +# Function: table\_getRowModel() + +```ts +function table_getRowModel(table): RowModel; +``` + +Defined in: [core/row-models/coreRowModelsFeature.utils.ts:255](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.utils.ts#L255) + +Returns row model for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`RowModel`](../../index/interfaces/RowModel.md)\<`TFeatures`, `TData`\> + +## Example + +```ts +const value = table_getRowModel(table) +``` diff --git a/docs/reference/static-functions/functions/table_getSelectedRowModel.md b/docs/reference/static-functions/functions/table_getSelectedRowModel.md new file mode 100644 index 0000000000..f11370f311 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getSelectedRowModel.md @@ -0,0 +1,42 @@ +--- +id: table_getSelectedRowModel +title: table_getSelectedRowModel +--- + +# Function: table\_getSelectedRowModel() + +```ts +function table_getSelectedRowModel(table): RowModel; +``` + +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:166](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L166) + +Returns selected row model for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`RowModel`](../../index/interfaces/RowModel.md)\<`TFeatures`, `TData`\> + +## Example + +```ts +const value = table_getSelectedRowModel(table) +``` diff --git a/docs/reference/static-functions/functions/table_getSortedRowModel.md b/docs/reference/static-functions/functions/table_getSortedRowModel.md new file mode 100644 index 0000000000..a7f94fe689 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getSortedRowModel.md @@ -0,0 +1,42 @@ +--- +id: table_getSortedRowModel +title: table_getSortedRowModel +--- + +# Function: table\_getSortedRowModel() + +```ts +function table_getSortedRowModel(table): RowModel; +``` + +Defined in: [core/row-models/coreRowModelsFeature.utils.ts:143](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/row-models/coreRowModelsFeature.utils.ts#L143) + +Returns sorted row model for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`RowModel`](../../index/interfaces/RowModel.md)\<`TFeatures`, `TData`\> + +## Example + +```ts +const value = table_getSortedRowModel(table) +``` diff --git a/docs/reference/static-functions/functions/table_getToggleAllColumnsVisibilityHandler.md b/docs/reference/static-functions/functions/table_getToggleAllColumnsVisibilityHandler.md new file mode 100644 index 0000000000..3d298e591e --- /dev/null +++ b/docs/reference/static-functions/functions/table_getToggleAllColumnsVisibilityHandler.md @@ -0,0 +1,54 @@ +--- +id: table_getToggleAllColumnsVisibilityHandler +title: table_getToggleAllColumnsVisibilityHandler +--- + +# Function: table\_getToggleAllColumnsVisibilityHandler() + +```ts +function table_getToggleAllColumnsVisibilityHandler(table): (e) => void; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.utils.ts:324](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.utils.ts#L324) + +Returns an event handler for all columns visibility handler. + +The handler calls the matching table toggle API and can be attached directly to checkbox or button UI. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +```ts +(e): void; +``` + +### Parameters + +#### e + +`unknown` + +### Returns + +`void` + +## Example + +```ts +const value = table_getToggleAllColumnsVisibilityHandler(table) +``` diff --git a/docs/reference/static-functions/functions/table_getToggleAllPageRowsSelectedHandler.md b/docs/reference/static-functions/functions/table_getToggleAllPageRowsSelectedHandler.md new file mode 100644 index 0000000000..4ac80702b9 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getToggleAllPageRowsSelectedHandler.md @@ -0,0 +1,54 @@ +--- +id: table_getToggleAllPageRowsSelectedHandler +title: table_getToggleAllPageRowsSelectedHandler +--- + +# Function: table\_getToggleAllPageRowsSelectedHandler() + +```ts +function table_getToggleAllPageRowsSelectedHandler(table): (e) => void; +``` + +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:379](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L379) + +Returns an event handler for all page rows selected handler. + +The handler calls the matching table toggle API and can be attached directly to checkbox or button UI. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +```ts +(e): void; +``` + +### Parameters + +#### e + +`unknown` + +### Returns + +`void` + +## Example + +```ts +const value = table_getToggleAllPageRowsSelectedHandler(table) +``` diff --git a/docs/reference/static-functions/functions/table_getToggleAllRowsExpandedHandler.md b/docs/reference/static-functions/functions/table_getToggleAllRowsExpandedHandler.md new file mode 100644 index 0000000000..2d5308c239 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getToggleAllRowsExpandedHandler.md @@ -0,0 +1,54 @@ +--- +id: table_getToggleAllRowsExpandedHandler +title: table_getToggleAllRowsExpandedHandler +--- + +# Function: table\_getToggleAllRowsExpandedHandler() + +```ts +function table_getToggleAllRowsExpandedHandler(table): (e) => void; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:136](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L136) + +Returns an event handler for all rows expanded handler. + +The handler calls the matching table toggle API and can be attached directly to checkbox or button UI. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +```ts +(e): void; +``` + +### Parameters + +#### e + +`unknown` + +### Returns + +`void` + +## Example + +```ts +const value = table_getToggleAllRowsExpandedHandler(table) +``` diff --git a/docs/reference/static-functions/functions/table_getToggleAllRowsSelectedHandler.md b/docs/reference/static-functions/functions/table_getToggleAllRowsSelectedHandler.md new file mode 100644 index 0000000000..53dacba73e --- /dev/null +++ b/docs/reference/static-functions/functions/table_getToggleAllRowsSelectedHandler.md @@ -0,0 +1,54 @@ +--- +id: table_getToggleAllRowsSelectedHandler +title: table_getToggleAllRowsSelectedHandler +--- + +# Function: table\_getToggleAllRowsSelectedHandler() + +```ts +function table_getToggleAllRowsSelectedHandler(table): (e) => void; +``` + +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:357](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L357) + +Returns an event handler for all rows selected handler. + +The handler calls the matching table toggle API and can be attached directly to checkbox or button UI. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +```ts +(e): void; +``` + +### Parameters + +#### e + +`unknown` + +### Returns + +`void` + +## Example + +```ts +const value = table_getToggleAllRowsSelectedHandler(table) +``` diff --git a/docs/reference/static-functions/functions/table_getTopRows.md b/docs/reference/static-functions/functions/table_getTopRows.md new file mode 100644 index 0000000000..4673365732 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getTopRows.md @@ -0,0 +1,42 @@ +--- +id: table_getTopRows +title: table_getTopRows +--- + +# Function: table\_getTopRows() + +```ts +function table_getTopRows(table): Row[]; +``` + +Defined in: [features/row-pinning/rowPinningFeature.utils.ts:144](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.utils.ts#L144) + +Returns top rows for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`Row`](../../index/type-aliases/Row.md)\<`TFeatures`, `TData`\>[] + +## Example + +```ts +const value = table_getTopRows(table) +``` diff --git a/docs/reference/static-functions/functions/table_getTotalSize.md b/docs/reference/static-functions/functions/table_getTotalSize.md new file mode 100644 index 0000000000..ea8160d29f --- /dev/null +++ b/docs/reference/static-functions/functions/table_getTotalSize.md @@ -0,0 +1,42 @@ +--- +id: table_getTotalSize +title: table_getTotalSize +--- + +# Function: table\_getTotalSize() + +```ts +function table_getTotalSize(table): number; +``` + +Defined in: [features/column-sizing/columnSizingFeature.utils.ts:268](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.utils.ts#L268) + +Returns total size for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`number` + +## Example + +```ts +const value = table_getTotalSize(table) +``` diff --git a/docs/reference/static-functions/functions/table_getVisibleFlatColumns.md b/docs/reference/static-functions/functions/table_getVisibleFlatColumns.md new file mode 100644 index 0000000000..0aa3954a5b --- /dev/null +++ b/docs/reference/static-functions/functions/table_getVisibleFlatColumns.md @@ -0,0 +1,42 @@ +--- +id: table_getVisibleFlatColumns +title: table_getVisibleFlatColumns +--- + +# Function: table\_getVisibleFlatColumns() + +```ts +function table_getVisibleFlatColumns(table): Column[]; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.utils.ts:170](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.utils.ts#L170) + +Returns visible flat columns for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`Column`](../../index/type-aliases/Column.md)\<`TFeatures`, `TData`, `unknown`\>[] + +## Example + +```ts +const value = table_getVisibleFlatColumns(table) +``` diff --git a/docs/reference/static-functions/functions/table_getVisibleLeafColumns.md b/docs/reference/static-functions/functions/table_getVisibleLeafColumns.md new file mode 100644 index 0000000000..dd6d787216 --- /dev/null +++ b/docs/reference/static-functions/functions/table_getVisibleLeafColumns.md @@ -0,0 +1,42 @@ +--- +id: table_getVisibleLeafColumns +title: table_getVisibleLeafColumns +--- + +# Function: table\_getVisibleLeafColumns() + +```ts +function table_getVisibleLeafColumns(table): Column[]; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.utils.ts:191](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.utils.ts#L191) + +Returns visible leaf columns for the table. + +This reads the relevant table atoms, options, and row-model cache to derive the current table-level value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +[`Column`](../../index/type-aliases/Column.md)\<`TFeatures`, `TData`, `unknown`\>[] + +## Example + +```ts +const value = table_getVisibleLeafColumns(table) +``` diff --git a/docs/reference/static-functions/functions/table_lastPage.md b/docs/reference/static-functions/functions/table_lastPage.md new file mode 100644 index 0000000000..6d439705c7 --- /dev/null +++ b/docs/reference/static-functions/functions/table_lastPage.md @@ -0,0 +1,42 @@ +--- +id: table_lastPage +title: table_lastPage +--- + +# Function: table\_lastPage() + +```ts +function table_lastPage(table): void; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.utils.ts:338](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.utils.ts#L338) + +Moves the table to the last known page. + +The target page is derived from `table_getPageCount(table) - 1`. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`void` + +## Example + +```ts +table_lastPage(table) +``` diff --git a/docs/reference/static-functions/functions/table_mergeOptions.md b/docs/reference/static-functions/functions/table_mergeOptions.md new file mode 100644 index 0000000000..dc22ae45d6 --- /dev/null +++ b/docs/reference/static-functions/functions/table_mergeOptions.md @@ -0,0 +1,47 @@ +--- +id: table_mergeOptions +title: table_mergeOptions +--- + +# Function: table\_mergeOptions() + +```ts +function table_mergeOptions(table, newOptions): TableOptions; +``` + +Defined in: [core/table/coreTablesFeature.utils.ts:76](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.utils.ts#L76) + +Merges new table options with the current resolved options. + +If `options.mergeOptions` is provided, it owns the merge behavior; otherwise +options are shallow-merged. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### newOptions + +[`TableOptions`](../../index/type-aliases/TableOptions.md)\<`TFeatures`, `TData`\> + +## Returns + +[`TableOptions`](../../index/type-aliases/TableOptions.md)\<`TFeatures`, `TData`\> + +## Example + +```ts +const value = table_mergeOptions(table) +``` diff --git a/docs/reference/static-functions/functions/table_nextPage.md b/docs/reference/static-functions/functions/table_nextPage.md new file mode 100644 index 0000000000..a7f7ea5261 --- /dev/null +++ b/docs/reference/static-functions/functions/table_nextPage.md @@ -0,0 +1,43 @@ +--- +id: table_nextPage +title: table_nextPage +--- + +# Function: table\_nextPage() + +```ts +function table_nextPage(table): void; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.utils.ts:302](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.utils.ts#L302) + +Moves the table to the next page. + +This delegates to `table_setPageIndex` so pagination state ownership and +updater semantics remain consistent. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`void` + +## Example + +```ts +table_nextPage(table) +``` diff --git a/docs/reference/static-functions/functions/table_previousPage.md b/docs/reference/static-functions/functions/table_previousPage.md new file mode 100644 index 0000000000..49dd50edf6 --- /dev/null +++ b/docs/reference/static-functions/functions/table_previousPage.md @@ -0,0 +1,43 @@ +--- +id: table_previousPage +title: table_previousPage +--- + +# Function: table\_previousPage() + +```ts +function table_previousPage(table): void; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.utils.ts:284](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.utils.ts#L284) + +Moves the table to the previous page. + +This delegates to `table_setPageIndex` so pagination state ownership and +updater semantics remain consistent. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`void` + +## Example + +```ts +table_previousPage(table) +``` diff --git a/docs/reference/static-functions/functions/table_reset.md b/docs/reference/static-functions/functions/table_reset.md new file mode 100644 index 0000000000..6324a9feec --- /dev/null +++ b/docs/reference/static-functions/functions/table_reset.md @@ -0,0 +1,43 @@ +--- +id: table_reset +title: table_reset +--- + +# Function: table\_reset() + +```ts +function table_reset(table): void; +``` + +Defined in: [core/table/coreTablesFeature.utils.ts:53](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.utils.ts#L53) + +Resets all internal table base atoms to `table.initialState`. + +This resets internally owned state slices in a single reactivity batch. Use +feature-specific reset APIs when a slice may be externally owned. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`void` + +## Example + +```ts +table_reset(table) +``` diff --git a/docs/reference/static-functions/functions/table_resetColumnFilters.md b/docs/reference/static-functions/functions/table_resetColumnFilters.md new file mode 100644 index 0000000000..0eb88cfc69 --- /dev/null +++ b/docs/reference/static-functions/functions/table_resetColumnFilters.md @@ -0,0 +1,47 @@ +--- +id: table_resetColumnFilters +title: table_resetColumnFilters +--- + +# Function: table\_resetColumnFilters() + +```ts +function table_resetColumnFilters(table, defaultState?): void; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:284](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L284) + +Resets the table's column filters state slice. + +By default the reset uses `table.initialState`; when supported, a blank/default reset bypasses the saved initial value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### defaultState? + +`boolean` + +## Returns + +`void` + +## Example + +```ts +table_resetColumnFilters(table) +table_resetColumnFilters(table, true) +``` diff --git a/docs/reference/static-functions/functions/table_resetColumnOrder.md b/docs/reference/static-functions/functions/table_resetColumnOrder.md new file mode 100644 index 0000000000..346d12fef7 --- /dev/null +++ b/docs/reference/static-functions/functions/table_resetColumnOrder.md @@ -0,0 +1,47 @@ +--- +id: table_resetColumnOrder +title: table_resetColumnOrder +--- + +# Function: table\_resetColumnOrder() + +```ts +function table_resetColumnOrder(table, defaultState?): void; +``` + +Defined in: [features/column-ordering/columnOrderingFeature.utils.ts:119](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-ordering/columnOrderingFeature.utils.ts#L119) + +Resets the table's column order state slice. + +By default the reset uses `table.initialState`; when supported, a blank/default reset bypasses the saved initial value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### defaultState? + +`boolean` + +## Returns + +`void` + +## Example + +```ts +table_resetColumnOrder(table) +table_resetColumnOrder(table, true) +``` diff --git a/docs/reference/static-functions/functions/table_resetColumnPinning.md b/docs/reference/static-functions/functions/table_resetColumnPinning.md new file mode 100644 index 0000000000..64a1dfd4b4 --- /dev/null +++ b/docs/reference/static-functions/functions/table_resetColumnPinning.md @@ -0,0 +1,47 @@ +--- +id: table_resetColumnPinning +title: table_resetColumnPinning +--- + +# Function: table\_resetColumnPinning() + +```ts +function table_resetColumnPinning(table, defaultState?): void; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:288](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L288) + +Resets the table's column pinning state slice. + +By default the reset uses `table.initialState`; when supported, a blank/default reset bypasses the saved initial value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### defaultState? + +`boolean` + +## Returns + +`void` + +## Example + +```ts +table_resetColumnPinning(table) +table_resetColumnPinning(table, true) +``` diff --git a/docs/reference/static-functions/functions/table_resetColumnSizing.md b/docs/reference/static-functions/functions/table_resetColumnSizing.md new file mode 100644 index 0000000000..6caef4e3d2 --- /dev/null +++ b/docs/reference/static-functions/functions/table_resetColumnSizing.md @@ -0,0 +1,47 @@ +--- +id: table_resetColumnSizing +title: table_resetColumnSizing +--- + +# Function: table\_resetColumnSizing() + +```ts +function table_resetColumnSizing(table, defaultState?): void; +``` + +Defined in: [features/column-sizing/columnSizingFeature.utils.ts:248](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.utils.ts#L248) + +Resets the table's column sizing state slice. + +By default the reset uses `table.initialState`; when supported, a blank/default reset bypasses the saved initial value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### defaultState? + +`boolean` + +## Returns + +`void` + +## Example + +```ts +table_resetColumnSizing(table) +table_resetColumnSizing(table, true) +``` diff --git a/docs/reference/static-functions/functions/table_resetColumnVisibility.md b/docs/reference/static-functions/functions/table_resetColumnVisibility.md new file mode 100644 index 0000000000..d2ecd07613 --- /dev/null +++ b/docs/reference/static-functions/functions/table_resetColumnVisibility.md @@ -0,0 +1,47 @@ +--- +id: table_resetColumnVisibility +title: table_resetColumnVisibility +--- + +# Function: table\_resetColumnVisibility() + +```ts +function table_resetColumnVisibility(table, defaultState?): void; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.utils.ts:233](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.utils.ts#L233) + +Resets the table's column visibility state slice. + +By default the reset uses `table.initialState`; when supported, a blank/default reset bypasses the saved initial value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### defaultState? + +`boolean` + +## Returns + +`void` + +## Example + +```ts +table_resetColumnVisibility(table) +table_resetColumnVisibility(table, true) +``` diff --git a/docs/reference/static-functions/functions/table_resetExpanded.md b/docs/reference/static-functions/functions/table_resetExpanded.md new file mode 100644 index 0000000000..39c095673a --- /dev/null +++ b/docs/reference/static-functions/functions/table_resetExpanded.md @@ -0,0 +1,47 @@ +--- +id: table_resetExpanded +title: table_resetExpanded +--- + +# Function: table\_resetExpanded() + +```ts +function table_resetExpanded(table, defaultState?): void; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:97](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L97) + +Resets the table's expanded state slice. + +By default the reset uses `table.initialState`; when supported, a blank/default reset bypasses the saved initial value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### defaultState? + +`boolean` + +## Returns + +`void` + +## Example + +```ts +table_resetExpanded(table) +table_resetExpanded(table, true) +``` diff --git a/docs/reference/static-functions/functions/table_resetGlobalFilter.md b/docs/reference/static-functions/functions/table_resetGlobalFilter.md new file mode 100644 index 0000000000..34ced8f7c4 --- /dev/null +++ b/docs/reference/static-functions/functions/table_resetGlobalFilter.md @@ -0,0 +1,47 @@ +--- +id: table_resetGlobalFilter +title: table_resetGlobalFilter +--- + +# Function: table\_resetGlobalFilter() + +```ts +function table_resetGlobalFilter(table, defaultState?): void; +``` + +Defined in: [features/global-filtering/globalFilteringFeature.utils.ts:102](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.utils.ts#L102) + +Resets the table's global filter state slice. + +By default the reset uses `table.initialState`; when supported, a blank/default reset bypasses the saved initial value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### defaultState? + +`boolean` + +## Returns + +`void` + +## Example + +```ts +table_resetGlobalFilter(table) +table_resetGlobalFilter(table, true) +``` diff --git a/docs/reference/static-functions/functions/table_resetGrouping.md b/docs/reference/static-functions/functions/table_resetGrouping.md new file mode 100644 index 0000000000..b7fd115c0a --- /dev/null +++ b/docs/reference/static-functions/functions/table_resetGrouping.md @@ -0,0 +1,47 @@ +--- +id: table_resetGrouping +title: table_resetGrouping +--- + +# Function: table\_resetGrouping() + +```ts +function table_resetGrouping(table, defaultState?): void; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:221](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L221) + +Resets the table's grouping state slice. + +By default the reset uses `table.initialState`; when supported, a blank/default reset bypasses the saved initial value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### defaultState? + +`boolean` + +## Returns + +`void` + +## Example + +```ts +table_resetGrouping(table) +table_resetGrouping(table, true) +``` diff --git a/docs/reference/static-functions/functions/table_resetHeaderSizeInfo.md b/docs/reference/static-functions/functions/table_resetHeaderSizeInfo.md new file mode 100644 index 0000000000..f67545d44a --- /dev/null +++ b/docs/reference/static-functions/functions/table_resetHeaderSizeInfo.md @@ -0,0 +1,47 @@ +--- +id: table_resetHeaderSizeInfo +title: table_resetHeaderSizeInfo +--- + +# Function: table\_resetHeaderSizeInfo() + +```ts +function table_resetHeaderSizeInfo(table, defaultState?): void; +``` + +Defined in: [features/column-resizing/columnResizingFeature.utils.ts:296](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.utils.ts#L296) + +Resets the table's header size info state slice. + +By default the reset uses `table.initialState`; when supported, a blank/default reset bypasses the saved initial value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### defaultState? + +`boolean` + +## Returns + +`void` + +## Example + +```ts +table_resetHeaderSizeInfo(table) +table_resetHeaderSizeInfo(table, true) +``` diff --git a/docs/reference/static-functions/functions/table_resetPageIndex.md b/docs/reference/static-functions/functions/table_resetPageIndex.md new file mode 100644 index 0000000000..2448950ae1 --- /dev/null +++ b/docs/reference/static-functions/functions/table_resetPageIndex.md @@ -0,0 +1,47 @@ +--- +id: table_resetPageIndex +title: table_resetPageIndex +--- + +# Function: table\_resetPageIndex() + +```ts +function table_resetPageIndex(table, defaultState?): void; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.utils.ts:141](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.utils.ts#L141) + +Resets the table's page index state slice. + +By default the reset uses `table.initialState`; when supported, a blank/default reset bypasses the saved initial value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### defaultState? + +`boolean` + +## Returns + +`void` + +## Example + +```ts +table_resetPageIndex(table) +table_resetPageIndex(table, true) +``` diff --git a/docs/reference/static-functions/functions/table_resetPageSize.md b/docs/reference/static-functions/functions/table_resetPageSize.md new file mode 100644 index 0000000000..ab67bde2b6 --- /dev/null +++ b/docs/reference/static-functions/functions/table_resetPageSize.md @@ -0,0 +1,47 @@ +--- +id: table_resetPageSize +title: table_resetPageSize +--- + +# Function: table\_resetPageSize() + +```ts +function table_resetPageSize(table, defaultState?): void; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.utils.ts:165](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.utils.ts#L165) + +Resets the table's page size state slice. + +By default the reset uses `table.initialState`; when supported, a blank/default reset bypasses the saved initial value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### defaultState? + +`boolean` + +## Returns + +`void` + +## Example + +```ts +table_resetPageSize(table) +table_resetPageSize(table, true) +``` diff --git a/docs/reference/static-functions/functions/table_resetPagination.md b/docs/reference/static-functions/functions/table_resetPagination.md new file mode 100644 index 0000000000..79c68de36a --- /dev/null +++ b/docs/reference/static-functions/functions/table_resetPagination.md @@ -0,0 +1,47 @@ +--- +id: table_resetPagination +title: table_resetPagination +--- + +# Function: table\_resetPagination() + +```ts +function table_resetPagination(table, defaultState?): void; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.utils.ts:84](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.utils.ts#L84) + +Resets the table's pagination state slice. + +By default the reset uses `table.initialState`; when supported, a blank/default reset bypasses the saved initial value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### defaultState? + +`boolean` + +## Returns + +`void` + +## Example + +```ts +table_resetPagination(table) +table_resetPagination(table, true) +``` diff --git a/docs/reference/static-functions/functions/table_resetRowPinning.md b/docs/reference/static-functions/functions/table_resetRowPinning.md new file mode 100644 index 0000000000..b4abb43c28 --- /dev/null +++ b/docs/reference/static-functions/functions/table_resetRowPinning.md @@ -0,0 +1,47 @@ +--- +id: table_resetRowPinning +title: table_resetRowPinning +--- + +# Function: table\_resetRowPinning() + +```ts +function table_resetRowPinning(table, defaultState?): void; +``` + +Defined in: [features/row-pinning/rowPinningFeature.utils.ts:62](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.utils.ts#L62) + +Resets the table's row pinning state slice. + +By default the reset uses `table.initialState`; when supported, a blank/default reset bypasses the saved initial value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### defaultState? + +`boolean` + +## Returns + +`void` + +## Example + +```ts +table_resetRowPinning(table) +table_resetRowPinning(table, true) +``` diff --git a/docs/reference/static-functions/functions/table_resetRowSelection.md b/docs/reference/static-functions/functions/table_resetRowSelection.md new file mode 100644 index 0000000000..d29ad590b5 --- /dev/null +++ b/docs/reference/static-functions/functions/table_resetRowSelection.md @@ -0,0 +1,47 @@ +--- +id: table_resetRowSelection +title: table_resetRowSelection +--- + +# Function: table\_resetRowSelection() + +```ts +function table_resetRowSelection(table, defaultState?): void; +``` + +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:56](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L56) + +Resets the table's row selection state slice. + +By default the reset uses `table.initialState`; when supported, a blank/default reset bypasses the saved initial value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### defaultState? + +`boolean` + +## Returns + +`void` + +## Example + +```ts +table_resetRowSelection(table) +table_resetRowSelection(table, true) +``` diff --git a/docs/reference/static-functions/functions/table_resetSorting.md b/docs/reference/static-functions/functions/table_resetSorting.md new file mode 100644 index 0000000000..c017e8c5b0 --- /dev/null +++ b/docs/reference/static-functions/functions/table_resetSorting.md @@ -0,0 +1,47 @@ +--- +id: table_resetSorting +title: table_resetSorting +--- + +# Function: table\_resetSorting() + +```ts +function table_resetSorting(table, defaultState?): void; +``` + +Defined in: [features/row-sorting/rowSortingFeature.utils.ts:57](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L57) + +Resets the table's sorting state slice. + +By default the reset uses `table.initialState`; when supported, a blank/default reset bypasses the saved initial value. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### defaultState? + +`boolean` + +## Returns + +`void` + +## Example + +```ts +table_resetSorting(table) +table_resetSorting(table, true) +``` diff --git a/docs/reference/static-functions/functions/table_setColumnFilters.md b/docs/reference/static-functions/functions/table_setColumnFilters.md new file mode 100644 index 0000000000..6d25d7f48a --- /dev/null +++ b/docs/reference/static-functions/functions/table_setColumnFilters.md @@ -0,0 +1,46 @@ +--- +id: table_setColumnFilters +title: table_setColumnFilters +--- + +# Function: table\_setColumnFilters() + +```ts +function table_setColumnFilters(table, updater): void; +``` + +Defined in: [features/column-filtering/columnFilteringFeature.utils.ts:245](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-filtering/columnFilteringFeature.utils.ts#L245) + +Updates the table's column filters state slice. + +The updater follows TanStack Table updater semantics and is routed through the corresponding `on*Change` option or backing atom. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### updater + +[`Updater`](../../index/type-aliases/Updater.md)\<[`ColumnFiltersState`](../../index/type-aliases/ColumnFiltersState.md)\> + +## Returns + +`void` + +## Example + +```ts +table_setColumnFilters(table, (old) => old) +``` diff --git a/docs/reference/static-functions/functions/table_setColumnOrder.md b/docs/reference/static-functions/functions/table_setColumnOrder.md new file mode 100644 index 0000000000..4ccf3e62fb --- /dev/null +++ b/docs/reference/static-functions/functions/table_setColumnOrder.md @@ -0,0 +1,46 @@ +--- +id: table_setColumnOrder +title: table_setColumnOrder +--- + +# Function: table\_setColumnOrder() + +```ts +function table_setColumnOrder(table, updater): void; +``` + +Defined in: [features/column-ordering/columnOrderingFeature.utils.ts:101](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-ordering/columnOrderingFeature.utils.ts#L101) + +Updates the table's column order state slice. + +The updater follows TanStack Table updater semantics and is routed through the corresponding `on*Change` option or backing atom. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### updater + +[`Updater`](../../index/type-aliases/Updater.md)\<[`ColumnOrderState`](../../index/type-aliases/ColumnOrderState.md)\> + +## Returns + +`void` + +## Example + +```ts +table_setColumnOrder(table, (old) => old) +``` diff --git a/docs/reference/static-functions/functions/table_setColumnPinning.md b/docs/reference/static-functions/functions/table_setColumnPinning.md new file mode 100644 index 0000000000..825809ee6f --- /dev/null +++ b/docs/reference/static-functions/functions/table_setColumnPinning.md @@ -0,0 +1,46 @@ +--- +id: table_setColumnPinning +title: table_setColumnPinning +--- + +# Function: table\_setColumnPinning() + +```ts +function table_setColumnPinning(table, updater): void; +``` + +Defined in: [features/column-pinning/columnPinningFeature.utils.ts:267](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-pinning/columnPinningFeature.utils.ts#L267) + +Updates the table's column pinning state slice. + +The updater follows TanStack Table updater semantics and is routed through the corresponding `on*Change` option or backing atom. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### updater + +[`Updater`](../../index/type-aliases/Updater.md)\<[`ColumnPinningState`](../../index/interfaces/ColumnPinningState.md)\> + +## Returns + +`void` + +## Example + +```ts +table_setColumnPinning(table, (old) => old) +``` diff --git a/docs/reference/static-functions/functions/table_setColumnResizing.md b/docs/reference/static-functions/functions/table_setColumnResizing.md new file mode 100644 index 0000000000..6bbbd7e849 --- /dev/null +++ b/docs/reference/static-functions/functions/table_setColumnResizing.md @@ -0,0 +1,46 @@ +--- +id: table_setColumnResizing +title: table_setColumnResizing +--- + +# Function: table\_setColumnResizing() + +```ts +function table_setColumnResizing(table, updater): void; +``` + +Defined in: [features/column-resizing/columnResizingFeature.utils.ts:275](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-resizing/columnResizingFeature.utils.ts#L275) + +Updates the table's column resizing state slice. + +The updater follows TanStack Table updater semantics and is routed through the corresponding `on*Change` option or backing atom. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### updater + +[`Updater`](../../index/type-aliases/Updater.md)\<[`columnResizingState`](../../index/interfaces/columnResizingState.md)\> + +## Returns + +`void` + +## Example + +```ts +table_setColumnResizing(table, (old) => old) +``` diff --git a/docs/reference/static-functions/functions/table_setColumnSizing.md b/docs/reference/static-functions/functions/table_setColumnSizing.md new file mode 100644 index 0000000000..035c319b17 --- /dev/null +++ b/docs/reference/static-functions/functions/table_setColumnSizing.md @@ -0,0 +1,46 @@ +--- +id: table_setColumnSizing +title: table_setColumnSizing +--- + +# Function: table\_setColumnSizing() + +```ts +function table_setColumnSizing(table, updater): void; +``` + +Defined in: [features/column-sizing/columnSizingFeature.utils.ts:227](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-sizing/columnSizingFeature.utils.ts#L227) + +Updates the table's column sizing state slice. + +The updater follows TanStack Table updater semantics and is routed through the corresponding `on*Change` option or backing atom. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### updater + +[`Updater`](../../index/type-aliases/Updater.md)\<[`ColumnSizingState`](../../index/type-aliases/ColumnSizingState.md)\> + +## Returns + +`void` + +## Example + +```ts +table_setColumnSizing(table, (old) => old) +``` diff --git a/docs/reference/static-functions/functions/table_setColumnVisibility.md b/docs/reference/static-functions/functions/table_setColumnVisibility.md new file mode 100644 index 0000000000..0941d5413e --- /dev/null +++ b/docs/reference/static-functions/functions/table_setColumnVisibility.md @@ -0,0 +1,46 @@ +--- +id: table_setColumnVisibility +title: table_setColumnVisibility +--- + +# Function: table\_setColumnVisibility() + +```ts +function table_setColumnVisibility(table, updater): void; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.utils.ts:212](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.utils.ts#L212) + +Updates the table's column visibility state slice. + +The updater follows TanStack Table updater semantics and is routed through the corresponding `on*Change` option or backing atom. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### updater + +[`Updater`](../../index/type-aliases/Updater.md)\<[`ColumnVisibilityState`](../../index/type-aliases/ColumnVisibilityState.md)\> + +## Returns + +`void` + +## Example + +```ts +table_setColumnVisibility(table, (old) => old) +``` diff --git a/docs/reference/static-functions/functions/table_setExpanded.md b/docs/reference/static-functions/functions/table_setExpanded.md new file mode 100644 index 0000000000..d33f135868 --- /dev/null +++ b/docs/reference/static-functions/functions/table_setExpanded.md @@ -0,0 +1,46 @@ +--- +id: table_setExpanded +title: table_setExpanded +--- + +# Function: table\_setExpanded() + +```ts +function table_setExpanded(table, updater): void; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:58](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L58) + +Updates the table's expanded state slice. + +The updater follows TanStack Table updater semantics and is routed through the corresponding `on*Change` option or backing atom. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### updater + +[`Updater`](../../index/type-aliases/Updater.md)\<[`ExpandedState`](../../index/type-aliases/ExpandedState.md)\> + +## Returns + +`void` + +## Example + +```ts +table_setExpanded(table, (old) => old) +``` diff --git a/docs/reference/static-functions/functions/table_setGlobalFilter.md b/docs/reference/static-functions/functions/table_setGlobalFilter.md new file mode 100644 index 0000000000..be3d7dc44f --- /dev/null +++ b/docs/reference/static-functions/functions/table_setGlobalFilter.md @@ -0,0 +1,46 @@ +--- +id: table_setGlobalFilter +title: table_setGlobalFilter +--- + +# Function: table\_setGlobalFilter() + +```ts +function table_setGlobalFilter(table, updater): void; +``` + +Defined in: [features/global-filtering/globalFilteringFeature.utils.ts:84](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/global-filtering/globalFilteringFeature.utils.ts#L84) + +Updates the table's global filter state slice. + +The updater follows TanStack Table updater semantics and is routed through the corresponding `on*Change` option or backing atom. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### updater + +`any` + +## Returns + +`void` + +## Example + +```ts +table_setGlobalFilter(table, (old) => old) +``` diff --git a/docs/reference/static-functions/functions/table_setGrouping.md b/docs/reference/static-functions/functions/table_setGrouping.md new file mode 100644 index 0000000000..23e46653fa --- /dev/null +++ b/docs/reference/static-functions/functions/table_setGrouping.md @@ -0,0 +1,46 @@ +--- +id: table_setGrouping +title: table_setGrouping +--- + +# Function: table\_setGrouping() + +```ts +function table_setGrouping(table, updater): void; +``` + +Defined in: [features/column-grouping/columnGroupingFeature.utils.ts:203](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-grouping/columnGroupingFeature.utils.ts#L203) + +Updates the table's grouping state slice. + +The updater follows TanStack Table updater semantics and is routed through the corresponding `on*Change` option or backing atom. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### updater + +[`Updater`](../../index/type-aliases/Updater.md)\<[`GroupingState`](../../index/type-aliases/GroupingState.md)\> + +## Returns + +`void` + +## Example + +```ts +table_setGrouping(table, (old) => old) +``` diff --git a/docs/reference/static-functions/functions/table_setOptions.md b/docs/reference/static-functions/functions/table_setOptions.md new file mode 100644 index 0000000000..cb19c606aa --- /dev/null +++ b/docs/reference/static-functions/functions/table_setOptions.md @@ -0,0 +1,47 @@ +--- +id: table_setOptions +title: table_setOptions +--- + +# Function: table\_setOptions() + +```ts +function table_setOptions(table, updater): void; +``` + +Defined in: [core/table/coreTablesFeature.utils.ts:104](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.utils.ts#L104) + +Updates the table options object. + +The updater receives the current resolved options and the merged result is +immediately assigned to the table instance. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### updater + +[`Updater`](../../index/type-aliases/Updater.md)\<[`TableOptions`](../../index/type-aliases/TableOptions.md)\<`TFeatures`, `TData`\>\> + +## Returns + +`void` + +## Example + +```ts +table_setOptions(table, (old) => old) +``` diff --git a/docs/reference/static-functions/functions/table_setPageIndex.md b/docs/reference/static-functions/functions/table_setPageIndex.md new file mode 100644 index 0000000000..706446d110 --- /dev/null +++ b/docs/reference/static-functions/functions/table_setPageIndex.md @@ -0,0 +1,46 @@ +--- +id: table_setPageIndex +title: table_setPageIndex +--- + +# Function: table\_setPageIndex() + +```ts +function table_setPageIndex(table, updater): void; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.utils.ts:108](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.utils.ts#L108) + +Updates the table's page index state slice. + +The updater follows TanStack Table updater semantics and is routed through the corresponding `on*Change` option or backing atom. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### updater + +[`Updater`](../../index/type-aliases/Updater.md)\<`number`\> + +## Returns + +`void` + +## Example + +```ts +table_setPageIndex(table, (old) => old) +``` diff --git a/docs/reference/static-functions/functions/table_setPageSize.md b/docs/reference/static-functions/functions/table_setPageSize.md new file mode 100644 index 0000000000..89536e43ff --- /dev/null +++ b/docs/reference/static-functions/functions/table_setPageSize.md @@ -0,0 +1,46 @@ +--- +id: table_setPageSize +title: table_setPageSize +--- + +# Function: table\_setPageSize() + +```ts +function table_setPageSize(table, updater): void; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.utils.ts:188](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.utils.ts#L188) + +Updates the table's page size state slice. + +The updater follows TanStack Table updater semantics and is routed through the corresponding `on*Change` option or backing atom. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### updater + +[`Updater`](../../index/type-aliases/Updater.md)\<`number`\> + +## Returns + +`void` + +## Example + +```ts +table_setPageSize(table, (old) => old) +``` diff --git a/docs/reference/static-functions/functions/table_setPagination.md b/docs/reference/static-functions/functions/table_setPagination.md new file mode 100644 index 0000000000..f00783937f --- /dev/null +++ b/docs/reference/static-functions/functions/table_setPagination.md @@ -0,0 +1,46 @@ +--- +id: table_setPagination +title: table_setPagination +--- + +# Function: table\_setPagination() + +```ts +function table_setPagination(table, updater): void | undefined; +``` + +Defined in: [features/row-pagination/rowPaginationFeature.utils.ts:60](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pagination/rowPaginationFeature.utils.ts#L60) + +Updates the table's pagination state slice. + +The updater follows TanStack Table updater semantics and is routed through the corresponding `on*Change` option or backing atom. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### updater + +[`Updater`](../../index/type-aliases/Updater.md)\<[`PaginationState`](../../index/interfaces/PaginationState.md)\> + +## Returns + +`void` \| `undefined` + +## Example + +```ts +table_setPagination(table, (old) => old) +``` diff --git a/docs/reference/static-functions/functions/table_setRowPinning.md b/docs/reference/static-functions/functions/table_setRowPinning.md new file mode 100644 index 0000000000..aedc30e651 --- /dev/null +++ b/docs/reference/static-functions/functions/table_setRowPinning.md @@ -0,0 +1,46 @@ +--- +id: table_setRowPinning +title: table_setRowPinning +--- + +# Function: table\_setRowPinning() + +```ts +function table_setRowPinning(table, updater): void; +``` + +Defined in: [features/row-pinning/rowPinningFeature.utils.ts:41](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-pinning/rowPinningFeature.utils.ts#L41) + +Updates the table's row pinning state slice. + +The updater follows TanStack Table updater semantics and is routed through the corresponding `on*Change` option or backing atom. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### updater + +[`Updater`](../../index/type-aliases/Updater.md)\<[`RowPinningState`](../../index/interfaces/RowPinningState.md)\> + +## Returns + +`void` + +## Example + +```ts +table_setRowPinning(table, (old) => old) +``` diff --git a/docs/reference/static-functions/functions/table_setRowSelection.md b/docs/reference/static-functions/functions/table_setRowSelection.md new file mode 100644 index 0000000000..3c57e8a33e --- /dev/null +++ b/docs/reference/static-functions/functions/table_setRowSelection.md @@ -0,0 +1,46 @@ +--- +id: table_setRowSelection +title: table_setRowSelection +--- + +# Function: table\_setRowSelection() + +```ts +function table_setRowSelection(table, updater): void; +``` + +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:35](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L35) + +Updates the table's row selection state slice. + +The updater follows TanStack Table updater semantics and is routed through the corresponding `on*Change` option or backing atom. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### updater + +[`Updater`](../../index/type-aliases/Updater.md)\<[`RowSelectionState`](../../index/type-aliases/RowSelectionState.md)\> + +## Returns + +`void` + +## Example + +```ts +table_setRowSelection(table, (old) => old) +``` diff --git a/docs/reference/static-functions/functions/table_setSorting.md b/docs/reference/static-functions/functions/table_setSorting.md new file mode 100644 index 0000000000..54b192fdf7 --- /dev/null +++ b/docs/reference/static-functions/functions/table_setSorting.md @@ -0,0 +1,46 @@ +--- +id: table_setSorting +title: table_setSorting +--- + +# Function: table\_setSorting() + +```ts +function table_setSorting(table, updater): void; +``` + +Defined in: [features/row-sorting/rowSortingFeature.utils.ts:39](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-sorting/rowSortingFeature.utils.ts#L39) + +Updates the table's sorting state slice. + +The updater follows TanStack Table updater semantics and is routed through the corresponding `on*Change` option or backing atom. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### updater + +[`Updater`](../../index/type-aliases/Updater.md)\<[`SortingState`](../../index/type-aliases/SortingState.md)\> + +## Returns + +`void` + +## Example + +```ts +table_setSorting(table, (old) => old) +``` diff --git a/docs/reference/static-functions/functions/table_syncExternalStateToBaseAtoms.md b/docs/reference/static-functions/functions/table_syncExternalStateToBaseAtoms.md new file mode 100644 index 0000000000..536bdfc675 --- /dev/null +++ b/docs/reference/static-functions/functions/table_syncExternalStateToBaseAtoms.md @@ -0,0 +1,43 @@ +--- +id: table_syncExternalStateToBaseAtoms +title: table_syncExternalStateToBaseAtoms +--- + +# Function: table\_syncExternalStateToBaseAtoms() + +```ts +function table_syncExternalStateToBaseAtoms(table): void; +``` + +Defined in: [core/table/coreTablesFeature.utils.ts:18](https://github.com/TanStack/table/blob/main/packages/table-core/src/core/table/coreTablesFeature.utils.ts#L18) + +Synchronizes externally controlled state slices into the table's base atoms. + +This keeps legacy `options.state` values reflected in the atom graph so +derived atoms, stores, and table APIs read a consistent snapshot. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +## Returns + +`void` + +## Example + +```ts +const value = table_syncExternalStateToBaseAtoms(table) +``` diff --git a/docs/reference/static-functions/functions/table_toggleAllColumnsVisible.md b/docs/reference/static-functions/functions/table_toggleAllColumnsVisible.md new file mode 100644 index 0000000000..59fa2a3a4c --- /dev/null +++ b/docs/reference/static-functions/functions/table_toggleAllColumnsVisible.md @@ -0,0 +1,46 @@ +--- +id: table_toggleAllColumnsVisible +title: table_toggleAllColumnsVisible +--- + +# Function: table\_toggleAllColumnsVisible() + +```ts +function table_toggleAllColumnsVisible(table, value?): void; +``` + +Defined in: [features/column-visibility/columnVisibilityFeature.utils.ts:253](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/column-visibility/columnVisibilityFeature.utils.ts#L253) + +Toggles all columns visible for the table. + +This is the table-level convenience API used by UI controls that affect many columns or rows at once. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### value? + +`boolean` + +## Returns + +`void` + +## Example + +```ts +table_toggleAllColumnsVisible(table) +``` diff --git a/docs/reference/static-functions/functions/table_toggleAllPageRowsSelected.md b/docs/reference/static-functions/functions/table_toggleAllPageRowsSelected.md new file mode 100644 index 0000000000..b56cc4557a --- /dev/null +++ b/docs/reference/static-functions/functions/table_toggleAllPageRowsSelected.md @@ -0,0 +1,46 @@ +--- +id: table_toggleAllPageRowsSelected +title: table_toggleAllPageRowsSelected +--- + +# Function: table\_toggleAllPageRowsSelected() + +```ts +function table_toggleAllPageRowsSelected(table, value?): void; +``` + +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:119](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L119) + +Toggles all page rows selected for the table. + +This is the table-level convenience API used by UI controls that affect many columns or rows at once. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### value? + +`boolean` + +## Returns + +`void` + +## Example + +```ts +table_toggleAllPageRowsSelected(table) +``` diff --git a/docs/reference/static-functions/functions/table_toggleAllRowsExpanded.md b/docs/reference/static-functions/functions/table_toggleAllRowsExpanded.md new file mode 100644 index 0000000000..3fdf2b60c3 --- /dev/null +++ b/docs/reference/static-functions/functions/table_toggleAllRowsExpanded.md @@ -0,0 +1,46 @@ +--- +id: table_toggleAllRowsExpanded +title: table_toggleAllRowsExpanded +--- + +# Function: table\_toggleAllRowsExpanded() + +```ts +function table_toggleAllRowsExpanded(table, expanded?): void; +``` + +Defined in: [features/row-expanding/rowExpandingFeature.utils.ts:75](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts#L75) + +Toggles all rows expanded for the table. + +This is the table-level convenience API used by UI controls that affect many columns or rows at once. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### expanded? + +`boolean` + +## Returns + +`void` + +## Example + +```ts +table_toggleAllRowsExpanded(table) +``` diff --git a/docs/reference/static-functions/functions/table_toggleAllRowsSelected.md b/docs/reference/static-functions/functions/table_toggleAllRowsSelected.md new file mode 100644 index 0000000000..e1cf5d682d --- /dev/null +++ b/docs/reference/static-functions/functions/table_toggleAllRowsSelected.md @@ -0,0 +1,46 @@ +--- +id: table_toggleAllRowsSelected +title: table_toggleAllRowsSelected +--- + +# Function: table\_toggleAllRowsSelected() + +```ts +function table_toggleAllRowsSelected(table, value?): void; +``` + +Defined in: [features/row-selection/rowSelectionFeature.utils.ts:78](https://github.com/TanStack/table/blob/main/packages/table-core/src/features/row-selection/rowSelectionFeature.utils.ts#L78) + +Toggles all rows selected for the table. + +This is the table-level convenience API used by UI controls that affect many columns or rows at once. + +## Type Parameters + +### TFeatures + +`TFeatures` *extends* [`TableFeatures`](../../index/interfaces/TableFeatures.md) + +### TData + +`TData` *extends* [`RowData`](../../index/type-aliases/RowData.md) + +## Parameters + +### table + +[`Table_Internal`](../../index/type-aliases/Table_Internal.md)\<`TFeatures`, `TData`\> + +### value? + +`boolean` + +## Returns + +`void` + +## Example + +```ts +table_toggleAllRowsSelected(table) +``` diff --git a/docs/reference/static-functions/index.md b/docs/reference/static-functions/index.md new file mode 100644 index 0000000000..1132ba9ba1 --- /dev/null +++ b/docs/reference/static-functions/index.md @@ -0,0 +1,249 @@ +--- +id: static-functions +title: static-functions +--- + +# static-functions + +## Functions + +- [cell\_getContext](functions/cell_getContext.md) +- [cell\_getIsAggregated](functions/cell_getIsAggregated.md) +- [cell\_getIsGrouped](functions/cell_getIsGrouped.md) +- [cell\_getIsPlaceholder](functions/cell_getIsPlaceholder.md) +- [cell\_getValue](functions/cell_getValue.md) +- [cell\_renderValue](functions/cell_renderValue.md) +- [column\_clearSorting](functions/column_clearSorting.md) +- [column\_getAfter](functions/column_getAfter.md) +- [column\_getAggregationFn](functions/column_getAggregationFn.md) +- [column\_getAutoAggregationFn](functions/column_getAutoAggregationFn.md) +- [column\_getAutoFilterFn](functions/column_getAutoFilterFn.md) +- [column\_getAutoSortDir](functions/column_getAutoSortDir.md) +- [column\_getAutoSortFn](functions/column_getAutoSortFn.md) +- [column\_getCanFilter](functions/column_getCanFilter.md) +- [column\_getCanGlobalFilter](functions/column_getCanGlobalFilter.md) +- [column\_getCanGroup](functions/column_getCanGroup.md) +- [column\_getCanHide](functions/column_getCanHide.md) +- [column\_getCanMultiSort](functions/column_getCanMultiSort.md) +- [column\_getCanPin](functions/column_getCanPin.md) +- [column\_getCanResize](functions/column_getCanResize.md) +- [column\_getCanSort](functions/column_getCanSort.md) +- [column\_getFacetedMinMaxValues](functions/column_getFacetedMinMaxValues.md) +- [column\_getFacetedRowModel](functions/column_getFacetedRowModel.md) +- [column\_getFacetedUniqueValues](functions/column_getFacetedUniqueValues.md) +- [column\_getFilterFn](functions/column_getFilterFn.md) +- [column\_getFilterIndex](functions/column_getFilterIndex.md) +- [column\_getFilterValue](functions/column_getFilterValue.md) +- [column\_getFirstSortDir](functions/column_getFirstSortDir.md) +- [column\_getFlatColumns](functions/column_getFlatColumns.md) +- [column\_getGroupedIndex](functions/column_getGroupedIndex.md) +- [column\_getIndex](functions/column_getIndex.md) +- [column\_getIsFiltered](functions/column_getIsFiltered.md) +- [column\_getIsFirstColumn](functions/column_getIsFirstColumn.md) +- [column\_getIsGrouped](functions/column_getIsGrouped.md) +- [column\_getIsLastColumn](functions/column_getIsLastColumn.md) +- [column\_getIsPinned](functions/column_getIsPinned.md) +- [column\_getIsResizing](functions/column_getIsResizing.md) +- [column\_getIsSorted](functions/column_getIsSorted.md) +- [column\_getIsVisible](functions/column_getIsVisible.md) +- [column\_getLeafColumns](functions/column_getLeafColumns.md) +- [column\_getNextSortingOrder](functions/column_getNextSortingOrder.md) +- [column\_getPinnedIndex](functions/column_getPinnedIndex.md) +- [column\_getSize](functions/column_getSize.md) +- [column\_getSortFn](functions/column_getSortFn.md) +- [column\_getSortIndex](functions/column_getSortIndex.md) +- [column\_getStart](functions/column_getStart.md) +- [column\_getToggleGroupingHandler](functions/column_getToggleGroupingHandler.md) +- [column\_getToggleSortingHandler](functions/column_getToggleSortingHandler.md) +- [column\_getToggleVisibilityHandler](functions/column_getToggleVisibilityHandler.md) +- [column\_pin](functions/column_pin.md) +- [column\_resetSize](functions/column_resetSize.md) +- [column\_setFilterValue](functions/column_setFilterValue.md) +- [column\_toggleGrouping](functions/column_toggleGrouping.md) +- [column\_toggleSorting](functions/column_toggleSorting.md) +- [column\_toggleVisibility](functions/column_toggleVisibility.md) +- [getDefaultColumnFiltersState](functions/getDefaultColumnFiltersState.md) +- [getDefaultColumnOrderState](functions/getDefaultColumnOrderState.md) +- [getDefaultColumnPinningState](functions/getDefaultColumnPinningState.md) +- [getDefaultColumnResizingState](functions/getDefaultColumnResizingState.md) +- [getDefaultColumnSizingColumnDef](functions/getDefaultColumnSizingColumnDef.md) +- [getDefaultColumnSizingState](functions/getDefaultColumnSizingState.md) +- [getDefaultColumnVisibilityState](functions/getDefaultColumnVisibilityState.md) +- [getDefaultExpandedState](functions/getDefaultExpandedState.md) +- [getDefaultGroupingState](functions/getDefaultGroupingState.md) +- [getDefaultPaginationState](functions/getDefaultPaginationState.md) +- [getDefaultRowPinningState](functions/getDefaultRowPinningState.md) +- [getDefaultRowSelectionState](functions/getDefaultRowSelectionState.md) +- [getDefaultSortingState](functions/getDefaultSortingState.md) +- [header\_getContext](functions/header_getContext.md) +- [header\_getLeafHeaders](functions/header_getLeafHeaders.md) +- [header\_getResizeHandler](functions/header_getResizeHandler.md) +- [header\_getSize](functions/header_getSize.md) +- [header\_getStart](functions/header_getStart.md) +- [isRowSelected](functions/isRowSelected.md) +- [isSubRowSelected](functions/isSubRowSelected.md) +- [isTouchStartEvent](functions/isTouchStartEvent.md) +- [orderColumns](functions/orderColumns.md) +- [passiveEventSupported](functions/passiveEventSupported.md) +- [row\_getAllCells](functions/row_getAllCells.md) +- [row\_getAllCellsByColumnId](functions/row_getAllCellsByColumnId.md) +- [row\_getAllVisibleCells](functions/row_getAllVisibleCells.md) +- [row\_getCanExpand](functions/row_getCanExpand.md) +- [row\_getCanMultiSelect](functions/row_getCanMultiSelect.md) +- [row\_getCanPin](functions/row_getCanPin.md) +- [row\_getCanSelect](functions/row_getCanSelect.md) +- [row\_getCanSelectSubRows](functions/row_getCanSelectSubRows.md) +- [row\_getCenterVisibleCells](functions/row_getCenterVisibleCells.md) +- [row\_getGroupingValue](functions/row_getGroupingValue.md) +- [row\_getIsAllParentsExpanded](functions/row_getIsAllParentsExpanded.md) +- [row\_getIsAllSubRowsSelected](functions/row_getIsAllSubRowsSelected.md) +- [row\_getIsExpanded](functions/row_getIsExpanded.md) +- [row\_getIsGrouped](functions/row_getIsGrouped.md) +- [row\_getIsPinned](functions/row_getIsPinned.md) +- [row\_getIsSelected](functions/row_getIsSelected.md) +- [row\_getIsSomeSelected](functions/row_getIsSomeSelected.md) +- [row\_getLeafRows](functions/row_getLeafRows.md) +- [row\_getLeftVisibleCells](functions/row_getLeftVisibleCells.md) +- [row\_getParentRow](functions/row_getParentRow.md) +- [row\_getParentRows](functions/row_getParentRows.md) +- [row\_getPinnedIndex](functions/row_getPinnedIndex.md) +- [row\_getRightVisibleCells](functions/row_getRightVisibleCells.md) +- [row\_getToggleExpandedHandler](functions/row_getToggleExpandedHandler.md) +- [row\_getToggleSelectedHandler](functions/row_getToggleSelectedHandler.md) +- [row\_getUniqueValues](functions/row_getUniqueValues.md) +- [row\_getValue](functions/row_getValue.md) +- [row\_getVisibleCells](functions/row_getVisibleCells.md) +- [row\_pin](functions/row_pin.md) +- [row\_renderValue](functions/row_renderValue.md) +- [row\_toggleExpanded](functions/row_toggleExpanded.md) +- [row\_toggleSelected](functions/row_toggleSelected.md) +- [selectRowsFn](functions/selectRowsFn.md) +- [shouldAutoRemoveFilter](functions/shouldAutoRemoveFilter.md) +- [table\_autoResetExpanded](functions/table_autoResetExpanded.md) +- [table\_autoResetPageIndex](functions/table_autoResetPageIndex.md) +- [table\_firstPage](functions/table_firstPage.md) +- [table\_getAllColumns](functions/table_getAllColumns.md) +- [table\_getAllFlatColumns](functions/table_getAllFlatColumns.md) +- [table\_getAllFlatColumnsById](functions/table_getAllFlatColumnsById.md) +- [table\_getAllLeafColumns](functions/table_getAllLeafColumns.md) +- [table\_getBottomRows](functions/table_getBottomRows.md) +- [table\_getCanNextPage](functions/table_getCanNextPage.md) +- [table\_getCanPreviousPage](functions/table_getCanPreviousPage.md) +- [table\_getCanSomeRowsExpand](functions/table_getCanSomeRowsExpand.md) +- [table\_getCenterFlatHeaders](functions/table_getCenterFlatHeaders.md) +- [table\_getCenterFooterGroups](functions/table_getCenterFooterGroups.md) +- [table\_getCenterHeaderGroups](functions/table_getCenterHeaderGroups.md) +- [table\_getCenterLeafColumns](functions/table_getCenterLeafColumns.md) +- [table\_getCenterLeafHeaders](functions/table_getCenterLeafHeaders.md) +- [table\_getCenterRows](functions/table_getCenterRows.md) +- [table\_getCenterTotalSize](functions/table_getCenterTotalSize.md) +- [table\_getCenterVisibleLeafColumns](functions/table_getCenterVisibleLeafColumns.md) +- [table\_getColumn](functions/table_getColumn.md) +- [table\_getCoreRowModel](functions/table_getCoreRowModel.md) +- [table\_getDefaultColumnDef](functions/table_getDefaultColumnDef.md) +- [table\_getExpandedDepth](functions/table_getExpandedDepth.md) +- [table\_getExpandedRowModel](functions/table_getExpandedRowModel.md) +- [table\_getFilteredRowModel](functions/table_getFilteredRowModel.md) +- [table\_getFilteredSelectedRowModel](functions/table_getFilteredSelectedRowModel.md) +- [table\_getFlatHeaders](functions/table_getFlatHeaders.md) +- [table\_getFooterGroups](functions/table_getFooterGroups.md) +- [table\_getGlobalAutoFilterFn](functions/table_getGlobalAutoFilterFn.md) +- [table\_getGlobalFacetedMinMaxValues](functions/table_getGlobalFacetedMinMaxValues.md) +- [table\_getGlobalFacetedRowModel](functions/table_getGlobalFacetedRowModel.md) +- [table\_getGlobalFacetedUniqueValues](functions/table_getGlobalFacetedUniqueValues.md) +- [table\_getGlobalFilterFn](functions/table_getGlobalFilterFn.md) +- [table\_getGroupedRowModel](functions/table_getGroupedRowModel.md) +- [table\_getGroupedSelectedRowModel](functions/table_getGroupedSelectedRowModel.md) +- [table\_getHeaderGroups](functions/table_getHeaderGroups.md) +- [table\_getIsAllColumnsVisible](functions/table_getIsAllColumnsVisible.md) +- [table\_getIsAllPageRowsSelected](functions/table_getIsAllPageRowsSelected.md) +- [table\_getIsAllRowsExpanded](functions/table_getIsAllRowsExpanded.md) +- [table\_getIsAllRowsSelected](functions/table_getIsAllRowsSelected.md) +- [table\_getIsSomeColumnsPinned](functions/table_getIsSomeColumnsPinned.md) +- [table\_getIsSomeColumnsVisible](functions/table_getIsSomeColumnsVisible.md) +- [table\_getIsSomePageRowsSelected](functions/table_getIsSomePageRowsSelected.md) +- [table\_getIsSomeRowsExpanded](functions/table_getIsSomeRowsExpanded.md) +- [table\_getIsSomeRowsPinned](functions/table_getIsSomeRowsPinned.md) +- [table\_getIsSomeRowsSelected](functions/table_getIsSomeRowsSelected.md) +- [table\_getLeafHeaders](functions/table_getLeafHeaders.md) +- [table\_getLeftFlatHeaders](functions/table_getLeftFlatHeaders.md) +- [table\_getLeftFooterGroups](functions/table_getLeftFooterGroups.md) +- [table\_getLeftHeaderGroups](functions/table_getLeftHeaderGroups.md) +- [table\_getLeftLeafColumns](functions/table_getLeftLeafColumns.md) +- [table\_getLeftLeafHeaders](functions/table_getLeftLeafHeaders.md) +- [table\_getLeftTotalSize](functions/table_getLeftTotalSize.md) +- [table\_getLeftVisibleLeafColumns](functions/table_getLeftVisibleLeafColumns.md) +- [table\_getOrderColumnsFn](functions/table_getOrderColumnsFn.md) +- [table\_getPageCount](functions/table_getPageCount.md) +- [table\_getPageOptions](functions/table_getPageOptions.md) +- [table\_getPaginatedRowModel](functions/table_getPaginatedRowModel.md) +- [table\_getPinnedLeafColumns](functions/table_getPinnedLeafColumns.md) +- [table\_getPinnedVisibleLeafColumns](functions/table_getPinnedVisibleLeafColumns.md) +- [table\_getPreExpandedRowModel](functions/table_getPreExpandedRowModel.md) +- [table\_getPreFilteredRowModel](functions/table_getPreFilteredRowModel.md) +- [table\_getPreGroupedRowModel](functions/table_getPreGroupedRowModel.md) +- [table\_getPrePaginatedRowModel](functions/table_getPrePaginatedRowModel.md) +- [table\_getPreSelectedRowModel](functions/table_getPreSelectedRowModel.md) +- [table\_getPreSortedRowModel](functions/table_getPreSortedRowModel.md) +- [table\_getRightFlatHeaders](functions/table_getRightFlatHeaders.md) +- [table\_getRightFooterGroups](functions/table_getRightFooterGroups.md) +- [table\_getRightHeaderGroups](functions/table_getRightHeaderGroups.md) +- [table\_getRightLeafColumns](functions/table_getRightLeafColumns.md) +- [table\_getRightLeafHeaders](functions/table_getRightLeafHeaders.md) +- [table\_getRightTotalSize](functions/table_getRightTotalSize.md) +- [table\_getRightVisibleLeafColumns](functions/table_getRightVisibleLeafColumns.md) +- [table\_getRow](functions/table_getRow.md) +- [table\_getRowCount](functions/table_getRowCount.md) +- [table\_getRowId](functions/table_getRowId.md) +- [table\_getRowModel](functions/table_getRowModel.md) +- [table\_getSelectedRowModel](functions/table_getSelectedRowModel.md) +- [table\_getSortedRowModel](functions/table_getSortedRowModel.md) +- [table\_getToggleAllColumnsVisibilityHandler](functions/table_getToggleAllColumnsVisibilityHandler.md) +- [table\_getToggleAllPageRowsSelectedHandler](functions/table_getToggleAllPageRowsSelectedHandler.md) +- [table\_getToggleAllRowsExpandedHandler](functions/table_getToggleAllRowsExpandedHandler.md) +- [table\_getToggleAllRowsSelectedHandler](functions/table_getToggleAllRowsSelectedHandler.md) +- [table\_getTopRows](functions/table_getTopRows.md) +- [table\_getTotalSize](functions/table_getTotalSize.md) +- [table\_getVisibleFlatColumns](functions/table_getVisibleFlatColumns.md) +- [table\_getVisibleLeafColumns](functions/table_getVisibleLeafColumns.md) +- [table\_lastPage](functions/table_lastPage.md) +- [table\_mergeOptions](functions/table_mergeOptions.md) +- [table\_nextPage](functions/table_nextPage.md) +- [table\_previousPage](functions/table_previousPage.md) +- [table\_reset](functions/table_reset.md) +- [table\_resetColumnFilters](functions/table_resetColumnFilters.md) +- [table\_resetColumnOrder](functions/table_resetColumnOrder.md) +- [table\_resetColumnPinning](functions/table_resetColumnPinning.md) +- [table\_resetColumnSizing](functions/table_resetColumnSizing.md) +- [table\_resetColumnVisibility](functions/table_resetColumnVisibility.md) +- [table\_resetExpanded](functions/table_resetExpanded.md) +- [table\_resetGlobalFilter](functions/table_resetGlobalFilter.md) +- [table\_resetGrouping](functions/table_resetGrouping.md) +- [table\_resetHeaderSizeInfo](functions/table_resetHeaderSizeInfo.md) +- [table\_resetPageIndex](functions/table_resetPageIndex.md) +- [table\_resetPageSize](functions/table_resetPageSize.md) +- [table\_resetPagination](functions/table_resetPagination.md) +- [table\_resetRowPinning](functions/table_resetRowPinning.md) +- [table\_resetRowSelection](functions/table_resetRowSelection.md) +- [table\_resetSorting](functions/table_resetSorting.md) +- [table\_setColumnFilters](functions/table_setColumnFilters.md) +- [table\_setColumnOrder](functions/table_setColumnOrder.md) +- [table\_setColumnPinning](functions/table_setColumnPinning.md) +- [table\_setColumnResizing](functions/table_setColumnResizing.md) +- [table\_setColumnSizing](functions/table_setColumnSizing.md) +- [table\_setColumnVisibility](functions/table_setColumnVisibility.md) +- [table\_setExpanded](functions/table_setExpanded.md) +- [table\_setGlobalFilter](functions/table_setGlobalFilter.md) +- [table\_setGrouping](functions/table_setGrouping.md) +- [table\_setOptions](functions/table_setOptions.md) +- [table\_setPageIndex](functions/table_setPageIndex.md) +- [table\_setPageSize](functions/table_setPageSize.md) +- [table\_setPagination](functions/table_setPagination.md) +- [table\_setRowPinning](functions/table_setRowPinning.md) +- [table\_setRowSelection](functions/table_setRowSelection.md) +- [table\_setSorting](functions/table_setSorting.md) +- [table\_syncExternalStateToBaseAtoms](functions/table_syncExternalStateToBaseAtoms.md) +- [table\_toggleAllColumnsVisible](functions/table_toggleAllColumnsVisible.md) +- [table\_toggleAllPageRowsSelected](functions/table_toggleAllPageRowsSelected.md) +- [table\_toggleAllRowsExpanded](functions/table_toggleAllRowsExpanded.md) +- [table\_toggleAllRowsSelected](functions/table_toggleAllRowsSelected.md) diff --git a/docs/vanilla.md b/docs/vanilla.md index 5f2262e52f..5c923d58c0 100644 --- a/docs/vanilla.md +++ b/docs/vanilla.md @@ -4,12 +4,12 @@ title: Vanilla TS/JS The `@tanstack/table-core` library contains the core logic for TanStack Table. If you are using a non-standard framework or don't have access to a framework, you can use the core library directly via TypeScript or JavaScript. -## `createTable` +## `_createTable` Takes an `options` object and returns a table. ```tsx -import { createTable } from '@tanstack/table-core' +import { _createTable } from '@tanstack/table-core' -const table = createTable(options) +const table = _createTable(options) ``` diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000000..d0bcf6bb2f --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,22 @@ +// @ts-check + +// @ts-ignore Needed due to moduleResolution Node vs Bundler +import { tanstackConfig } from '@tanstack/eslint-config' + +/** @type {any} */ +const config = [ + ...tanstackConfig, + { + name: 'tanstack/temp', + rules: { + 'no-case-declarations': 'off', + 'no-shadow': 'off', + '@typescript-eslint/naming-convention': 'off', + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/no-unnecessary-condition': 'warn', + '@typescript-eslint/no-unsafe-function-type': 'off', + }, + }, +] + +export default config diff --git a/examples/angular/basic-app-table/.gitignore b/examples/angular/basic-app-table/.gitignore new file mode 100644 index 0000000000..854acd5fc0 --- /dev/null +++ b/examples/angular/basic-app-table/.gitignore @@ -0,0 +1,44 @@ +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out + +# Node +/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/mcp.json +.history/* + +# Miscellaneous +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings +__screenshots__/ + +# System files +.DS_Store +Thumbs.db diff --git a/examples/angular/basic-app-table/README.md b/examples/angular/basic-app-table/README.md new file mode 100644 index 0000000000..7ba2b2ea74 --- /dev/null +++ b/examples/angular/basic-app-table/README.md @@ -0,0 +1,59 @@ +# CustomPlugin + +This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.1.0. + +## Development server + +To start a local development server, run: + +```bash +ng serve +``` + +Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files. + +## Code scaffolding + +Angular CLI includes powerful code scaffolding tools. To generate a new component, run: + +```bash +ng generate component component-name +``` + +For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run: + +```bash +ng generate --help +``` + +## Building + +To build the project run: + +```bash +ng build +``` + +This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed. + +## Running unit tests + +To execute unit tests with the [Vitest](https://vitest.dev/) test runner, use the following command: + +```bash +ng test +``` + +## Running end-to-end tests + +For end-to-end (e2e) testing, run: + +```bash +ng e2e +``` + +Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs. + +## Additional Resources + +For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. diff --git a/examples/angular/basic-app-table/angular.json b/examples/angular/basic-app-table/angular.json new file mode 100644 index 0000000000..13f79bba70 --- /dev/null +++ b/examples/angular/basic-app-table/angular.json @@ -0,0 +1,99 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "cli": { + "packageManager": "pnpm", + "analytics": false, + "cache": { + "enabled": false + } + }, + "newProjectRoot": "projects", + "projects": { + "basic-app-table": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "inlineTemplate": true, + "inlineStyle": true, + "skipTests": true + }, + "@schematics/angular:class": { + "skipTests": true + }, + "@schematics/angular:directive": { + "skipTests": true + }, + "@schematics/angular:guard": { + "skipTests": true + }, + "@schematics/angular:interceptor": { + "skipTests": true + }, + "@schematics/angular:pipe": { + "skipTests": true + }, + "@schematics/angular:resolver": { + "skipTests": true + }, + "@schematics/angular:service": { + "skipTests": true + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular/build:application", + "options": { + "browser": "src/main.ts", + "tsConfig": "tsconfig.app.json", + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": ["src/styles.css"] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kB", + "maximumError": "8kB" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular/build:dev-server", + "configurations": { + "production": { + "buildTarget": "basic-app-table:build:production" + }, + "development": { + "buildTarget": "basic-app-table:build:development" + } + }, + "defaultConfiguration": "development" + } + } + } + } +} diff --git a/examples/angular/basic-app-table/package.json b/examples/angular/basic-app-table/package.json new file mode 100644 index 0000000000..a4b5cbb396 --- /dev/null +++ b/examples/angular/basic-app-table/package.json @@ -0,0 +1,30 @@ +{ + "name": "tanstack-angular-table-example-basic-app-table", + "scripts": { + "ng": "ng", + "start": "ng serve --port 5173", + "dev": "ng serve --port 5173", + "build": "ng build", + "lint": "eslint ./src", + "watch": "ng build --watch --configuration development" + }, + "private": true, + "packageManager": "pnpm@11.0.9", + "dependencies": { + "@angular/common": "^21.2.12", + "@angular/compiler": "^21.2.12", + "@angular/core": "^21.2.12", + "@angular/forms": "^21.2.12", + "@angular/platform-browser": "^21.2.12", + "@faker-js/faker": "^10.4.0", + "@tanstack/angular-table": "^9.0.0-alpha.45", + "rxjs": "~7.8.2", + "tslib": "^2.8.1" + }, + "devDependencies": { + "@angular/build": "^21.2.10", + "@angular/cli": "^21.2.10", + "@angular/compiler-cli": "^21.2.12", + "typescript": "6.0.3" + } +} diff --git a/examples/angular/basic/src/favicon.ico b/examples/angular/basic-app-table/public/favicon.ico similarity index 100% rename from examples/angular/basic/src/favicon.ico rename to examples/angular/basic-app-table/public/favicon.ico diff --git a/examples/angular/basic-app-table/src/app/app.config.ts b/examples/angular/basic-app-table/src/app/app.config.ts new file mode 100644 index 0000000000..cbb47d366c --- /dev/null +++ b/examples/angular/basic-app-table/src/app/app.config.ts @@ -0,0 +1,6 @@ +import { provideBrowserGlobalErrorListeners } from '@angular/core' +import type { ApplicationConfig } from '@angular/core' + +export const appConfig: ApplicationConfig = { + providers: [provideBrowserGlobalErrorListeners()], +} diff --git a/examples/angular/basic-app-table/src/app/app.html b/examples/angular/basic-app-table/src/app/app.html new file mode 100644 index 0000000000..bb2de8903e --- /dev/null +++ b/examples/angular/basic-app-table/src/app/app.html @@ -0,0 +1,42 @@ +
+ + +
+
+ + @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { + + @for (header of headerGroup.headers; track header.id) { + @if (!header.isPlaceholder) { + + } + } + + } + + + @for (row of table.getRowModel().rows; track row.id) { + + @for (cell of row.getAllCells(); track cell.id) { + + } + + } + + + @for (footerGroup of table.getFooterGroups(); track footerGroup.id) { + + @for (footer of footerGroup.headers; track footer.id) { + + } + + } + +
+
+
+
+
+ {{ footer }} +
+ diff --git a/examples/angular/basic-app-table/src/app/app.ts b/examples/angular/basic-app-table/src/app/app.ts new file mode 100644 index 0000000000..cb578b1920 --- /dev/null +++ b/examples/angular/basic-app-table/src/app/app.ts @@ -0,0 +1,71 @@ +import { ChangeDetectionStrategy, Component, signal } from '@angular/core' +import { FlexRender, createTableHook } from '@tanstack/angular-table' +import { makeData } from './makeData' +import type { Person } from './makeData' + +// 3. New in V9! Tell the table which features and row models we want to use. +// In this case, this will be a basic table with no additional features +const { injectAppTable, createAppColumnHelper } = createTableHook({ + _features: {}, + _rowModels: {}, // client-side row models. `Core` row model is now included by default, but you can still override it here + debugTable: true, +}) + +// 4. Create a helper object to help define our columns +const columnHelper = createAppColumnHelper() + +// 5. Define the columns for your table with a stable reference (in this case, defined statically outside of a react component) +const columns = columnHelper.columns([ + // accessorKey method (most common for simple use-cases) + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (info) => info.column.id, + }), + // accessorFn used (alternative) along with a custom id + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => `${info.getValue()}`, + header: () => `Last Name`, + footer: (info) => info.column.id, + }), + // accessorFn used to transform the data + columnHelper.accessor((row) => Number(row.age), { + id: 'age', + header: () => 'Age', + cell: (info) => info.renderValue(), + footer: (info) => info.column.id, + }), + columnHelper.accessor('visits', { + header: () => `Visits`, + footer: (info) => info.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (info) => info.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (info) => info.column.id, + }), +]) + +@Component({ + selector: 'app-root', + imports: [FlexRender], + templateUrl: './app.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class App { + readonly data = signal>(makeData(20)) + + // 6. Create the table instance with the required columns and data. + // Features and row models are already defined in the createTableHook call above + readonly table = injectAppTable(() => ({ + columns, + data: this.data(), + // add additional table options here or in the createTableHook call above + })) + + refreshData = () => this.data.set(makeData(20)) + stressTest = () => this.data.set(makeData(1_000)) +} diff --git a/examples/angular/basic-app-table/src/app/makeData.ts b/examples/angular/basic-app-table/src/app/makeData.ts new file mode 100644 index 0000000000..b9fb014aba --- /dev/null +++ b/examples/angular/basic-app-table/src/app/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/angular/basic-app-table/src/index.html b/examples/angular/basic-app-table/src/index.html new file mode 100644 index 0000000000..0d823f0573 --- /dev/null +++ b/examples/angular/basic-app-table/src/index.html @@ -0,0 +1,13 @@ + + + + + Basic App Table + + + + + + + + diff --git a/examples/angular/basic-app-table/src/main.ts b/examples/angular/basic-app-table/src/main.ts new file mode 100644 index 0000000000..8192dca694 --- /dev/null +++ b/examples/angular/basic-app-table/src/main.ts @@ -0,0 +1,5 @@ +import { bootstrapApplication } from '@angular/platform-browser' +import { appConfig } from './app/app.config' +import { App } from './app/app' + +bootstrapApplication(App, appConfig).catch((err) => console.error(err)) diff --git a/examples/angular/basic-app-table/src/styles.css b/examples/angular/basic-app-table/src/styles.css new file mode 100644 index 0000000000..3546a66331 --- /dev/null +++ b/examples/angular/basic-app-table/src/styles.css @@ -0,0 +1,371 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +.pagination-actions { + margin: 10px; + display: flex; + gap: 10px; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/angular/basic-app-table/tsconfig.app.json b/examples/angular/basic-app-table/tsconfig.app.json new file mode 100644 index 0000000000..a0dcc37c60 --- /dev/null +++ b/examples/angular/basic-app-table/tsconfig.app.json @@ -0,0 +1,11 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] +} diff --git a/examples/angular/basic-app-table/tsconfig.json b/examples/angular/basic-app-table/tsconfig.json new file mode 100644 index 0000000000..32789cdc2b --- /dev/null +++ b/examples/angular/basic-app-table/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "isolatedModules": true, + "experimentalDecorators": true, + "importHelpers": true, + "target": "ES2022", + "module": "preserve" + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + }, + "include": ["**/*.ts", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/angular/basic-inject-table/.gitignore b/examples/angular/basic-inject-table/.gitignore new file mode 100644 index 0000000000..854acd5fc0 --- /dev/null +++ b/examples/angular/basic-inject-table/.gitignore @@ -0,0 +1,44 @@ +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out + +# Node +/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/mcp.json +.history/* + +# Miscellaneous +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings +__screenshots__/ + +# System files +.DS_Store +Thumbs.db diff --git a/examples/angular/basic-inject-table/README.md b/examples/angular/basic-inject-table/README.md new file mode 100644 index 0000000000..7ba2b2ea74 --- /dev/null +++ b/examples/angular/basic-inject-table/README.md @@ -0,0 +1,59 @@ +# CustomPlugin + +This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.1.0. + +## Development server + +To start a local development server, run: + +```bash +ng serve +``` + +Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files. + +## Code scaffolding + +Angular CLI includes powerful code scaffolding tools. To generate a new component, run: + +```bash +ng generate component component-name +``` + +For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run: + +```bash +ng generate --help +``` + +## Building + +To build the project run: + +```bash +ng build +``` + +This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed. + +## Running unit tests + +To execute unit tests with the [Vitest](https://vitest.dev/) test runner, use the following command: + +```bash +ng test +``` + +## Running end-to-end tests + +For end-to-end (e2e) testing, run: + +```bash +ng e2e +``` + +Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs. + +## Additional Resources + +For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. diff --git a/examples/angular/basic-inject-table/angular.json b/examples/angular/basic-inject-table/angular.json new file mode 100644 index 0000000000..a9b3440e1d --- /dev/null +++ b/examples/angular/basic-inject-table/angular.json @@ -0,0 +1,99 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "cli": { + "packageManager": "pnpm", + "analytics": false, + "cache": { + "enabled": false + } + }, + "newProjectRoot": "projects", + "projects": { + "basic": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "inlineTemplate": true, + "inlineStyle": true, + "skipTests": true + }, + "@schematics/angular:class": { + "skipTests": true + }, + "@schematics/angular:directive": { + "skipTests": true + }, + "@schematics/angular:guard": { + "skipTests": true + }, + "@schematics/angular:interceptor": { + "skipTests": true + }, + "@schematics/angular:pipe": { + "skipTests": true + }, + "@schematics/angular:resolver": { + "skipTests": true + }, + "@schematics/angular:service": { + "skipTests": true + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular/build:application", + "options": { + "browser": "src/main.ts", + "tsConfig": "tsconfig.app.json", + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": ["src/styles.css"] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kB", + "maximumError": "8kB" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular/build:dev-server", + "configurations": { + "production": { + "buildTarget": "basic:build:production" + }, + "development": { + "buildTarget": "basic:build:development" + } + }, + "defaultConfiguration": "development" + } + } + } + } +} diff --git a/examples/angular/basic-inject-table/package.json b/examples/angular/basic-inject-table/package.json new file mode 100644 index 0000000000..9dc849e883 --- /dev/null +++ b/examples/angular/basic-inject-table/package.json @@ -0,0 +1,31 @@ +{ + "name": "tanstack-angular-table-example-basic-inject-table", + "scripts": { + "ng": "ng", + "start": "ng serve --port 5173", + "dev": "ng serve --port 5173", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "lint": "eslint ./src" + }, + "private": true, + "packageManager": "pnpm@11.0.9", + "dependencies": { + "@angular/common": "^21.2.12", + "@angular/compiler": "^21.2.12", + "@angular/core": "^21.2.12", + "@angular/forms": "^21.2.12", + "@angular/platform-browser": "^21.2.12", + "@angular/router": "^21.2.12", + "@faker-js/faker": "^10.4.0", + "@tanstack/angular-table": "^9.0.0-alpha.45", + "rxjs": "~7.8.2", + "tslib": "^2.8.1" + }, + "devDependencies": { + "@angular/build": "^21.2.10", + "@angular/cli": "^21.2.10", + "@angular/compiler-cli": "^21.2.12", + "typescript": "6.0.3" + } +} diff --git a/examples/angular/column-ordering/src/favicon.ico b/examples/angular/basic-inject-table/public/favicon.ico similarity index 100% rename from examples/angular/column-ordering/src/favicon.ico rename to examples/angular/basic-inject-table/public/favicon.ico diff --git a/examples/angular/basic-inject-table/src/app/app.config.ts b/examples/angular/basic-inject-table/src/app/app.config.ts new file mode 100644 index 0000000000..00f6004def --- /dev/null +++ b/examples/angular/basic-inject-table/src/app/app.config.ts @@ -0,0 +1,8 @@ +import { provideRouter } from '@angular/router' +import { provideBrowserGlobalErrorListeners } from '@angular/core' +import { routes } from './app.routes' +import type { ApplicationConfig } from '@angular/core' + +export const appConfig: ApplicationConfig = { + providers: [provideBrowserGlobalErrorListeners(), provideRouter(routes)], +} diff --git a/examples/angular/basic-inject-table/src/app/app.html b/examples/angular/basic-inject-table/src/app/app.html new file mode 100644 index 0000000000..bb2de8903e --- /dev/null +++ b/examples/angular/basic-inject-table/src/app/app.html @@ -0,0 +1,42 @@ +
+ + +
+ + + @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { + + @for (header of headerGroup.headers; track header.id) { + @if (!header.isPlaceholder) { + + } + } + + } + + + @for (row of table.getRowModel().rows; track row.id) { + + @for (cell of row.getAllCells(); track cell.id) { + + } + + } + + + @for (footerGroup of table.getFooterGroups(); track footerGroup.id) { + + @for (footer of footerGroup.headers; track footer.id) { + + } + + } + +
+
+
+
+
+ {{ footer }} +
+
diff --git a/examples/angular/basic-inject-table/src/app/app.routes.ts b/examples/angular/basic-inject-table/src/app/app.routes.ts new file mode 100644 index 0000000000..9a884b1131 --- /dev/null +++ b/examples/angular/basic-inject-table/src/app/app.routes.ts @@ -0,0 +1,3 @@ +import type { Routes } from '@angular/router' + +export const routes: Routes = [] diff --git a/examples/angular/basic-inject-table/src/app/app.ts b/examples/angular/basic-inject-table/src/app/app.ts new file mode 100644 index 0000000000..44cce9194c --- /dev/null +++ b/examples/angular/basic-inject-table/src/app/app.ts @@ -0,0 +1,69 @@ +import { ChangeDetectionStrategy, Component, signal } from '@angular/core' +import { FlexRender, injectTable, tableFeatures } from '@tanstack/angular-table' +import { makeData } from './makeData' +import type { Person } from './makeData' +import type { ColumnDef } from '@tanstack/angular-table' + +// 3. New in V9! Tell the table which features and row models we want to use. +// In this case, this will be a basic table with no additional features +const _features = tableFeatures({}) // util method to create sharable TFeatures object/type + +// 4. Define the columns for your table. This uses the new `ColumnDef` type to define columns. +// Alternatively, check out the createAppTable/createColumnHelper util for an even more type-safe way to define columns. +const defaultColumns: Array> = [ + { + accessorKey: 'firstName', + cell: (info) => info.getValue(), + footer: (info) => info.column.id, + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => `${info.getValue()}`, + header: () => `Last Name`, + footer: (info) => info.column.id, + }, + { + accessorKey: 'age', + header: () => 'Age', + footer: (info) => info.column.id, + }, + { + accessorKey: 'visits', + header: () => `Visits`, + footer: (info) => info.column.id, + }, + { + accessorKey: 'status', + header: 'Status', + footer: (info) => info.column.id, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + footer: (info) => info.column.id, + }, +] + +@Component({ + selector: 'app-root', + imports: [FlexRender], + templateUrl: './app.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class App { + readonly data = signal>(makeData(20)) + + // 5. Create the table instance with required _features, columns, and data + table = injectTable(() => ({ + debugTable: true, + _features, // new required option in V9. Tell the table which features you are importing and using (better tree-shaking) + _rowModels: {}, // `Core` row model is now included by default, but you can still override it here + data: this.data(), + columns: defaultColumns, + // ...other options here + })) + + refreshData = () => this.data.set(makeData(20)) + stressTest = () => this.data.set(makeData(1_000)) +} diff --git a/examples/angular/basic-inject-table/src/app/makeData.ts b/examples/angular/basic-inject-table/src/app/makeData.ts new file mode 100644 index 0000000000..b9fb014aba --- /dev/null +++ b/examples/angular/basic-inject-table/src/app/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/angular/basic-inject-table/src/index.html b/examples/angular/basic-inject-table/src/index.html new file mode 100644 index 0000000000..1b3f817b9e --- /dev/null +++ b/examples/angular/basic-inject-table/src/index.html @@ -0,0 +1,13 @@ + + + + + Basic + + + + + + + + diff --git a/examples/angular/basic-inject-table/src/main.ts b/examples/angular/basic-inject-table/src/main.ts new file mode 100644 index 0000000000..8192dca694 --- /dev/null +++ b/examples/angular/basic-inject-table/src/main.ts @@ -0,0 +1,5 @@ +import { bootstrapApplication } from '@angular/platform-browser' +import { appConfig } from './app/app.config' +import { App } from './app/app' + +bootstrapApplication(App, appConfig).catch((err) => console.error(err)) diff --git a/examples/angular/basic-inject-table/src/styles.css b/examples/angular/basic-inject-table/src/styles.css new file mode 100644 index 0000000000..3546a66331 --- /dev/null +++ b/examples/angular/basic-inject-table/src/styles.css @@ -0,0 +1,371 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +.pagination-actions { + margin: 10px; + display: flex; + gap: 10px; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/angular/basic-inject-table/tsconfig.app.json b/examples/angular/basic-inject-table/tsconfig.app.json new file mode 100644 index 0000000000..a0dcc37c60 --- /dev/null +++ b/examples/angular/basic-inject-table/tsconfig.app.json @@ -0,0 +1,11 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] +} diff --git a/examples/angular/basic-inject-table/tsconfig.json b/examples/angular/basic-inject-table/tsconfig.json new file mode 100644 index 0000000000..32789cdc2b --- /dev/null +++ b/examples/angular/basic-inject-table/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "isolatedModules": true, + "experimentalDecorators": true, + "importHelpers": true, + "target": "ES2022", + "module": "preserve" + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + }, + "include": ["**/*.ts", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/angular/basic/.devcontainer/devcontainer.json b/examples/angular/basic/.devcontainer/devcontainer.json deleted file mode 100644 index 36f47d8762..0000000000 --- a/examples/angular/basic/.devcontainer/devcontainer.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "Node.js", - "image": "mcr.microsoft.com/devcontainers/javascript-node:18" -} diff --git a/examples/angular/basic/.editorconfig b/examples/angular/basic/.editorconfig deleted file mode 100644 index 59d9a3a3e7..0000000000 --- a/examples/angular/basic/.editorconfig +++ /dev/null @@ -1,16 +0,0 @@ -# Editor configuration, see https://editorconfig.org -root = true - -[*] -charset = utf-8 -indent_style = space -indent_size = 2 -insert_final_newline = true -trim_trailing_whitespace = true - -[*.ts] -quote_type = single - -[*.md] -max_line_length = off -trim_trailing_whitespace = false diff --git a/examples/angular/basic/angular.json b/examples/angular/basic/angular.json deleted file mode 100644 index 7197e2fe77..0000000000 --- a/examples/angular/basic/angular.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "basic": { - "cli": { - "cache": { - "enabled": false - } - }, - "projectType": "application", - "schematics": { - "@schematics/angular:component": { - "style": "scss" - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:application", - "options": { - "outputPath": "dist/basic", - "index": "src/index.html", - "browser": "src/main.ts", - "polyfills": ["zone.js"], - "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": ["src/favicon.ico", "src/assets"], - "styles": ["src/styles.scss"], - "scripts": [] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" - }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" - } - ], - "outputHashing": "all" - }, - "development": { - "optimization": false, - "extractLicenses": false, - "sourceMap": true - } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "buildTarget": "basic:build:production" - }, - "development": { - "buildTarget": "basic:build:development" - } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "buildTarget": "basic:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "polyfills": ["zone.js", "zone.js/testing"], - "tsConfig": "tsconfig.spec.json", - "inlineStyleLanguage": "scss", - "assets": ["src/favicon.ico", "src/assets"], - "styles": ["src/styles.scss"], - "scripts": [] - } - } - } - } - }, - "cli": { - "analytics": false - } -} diff --git a/examples/angular/basic/package.json b/examples/angular/basic/package.json deleted file mode 100644 index e713398656..0000000000 --- a/examples/angular/basic/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "tanstack-table-example-angular-basic", - "version": "0.0.0", - "scripts": { - "ng": "ng", - "start": "ng serve", - "build": "ng build", - "watch": "ng build --watch --configuration development", - "test": "ng test" - }, - "private": true, - "dependencies": { - "@angular/animations": "^17.3.9", - "@angular/common": "^17.3.9", - "@angular/compiler": "^17.3.9", - "@angular/core": "^17.3.9", - "@angular/forms": "^17.3.9", - "@angular/platform-browser": "^17.3.9", - "@angular/platform-browser-dynamic": "^17.3.9", - "@angular/router": "^17.3.9", - "@tanstack/angular-table": "^8.20.5", - "rxjs": "~7.8.1", - "zone.js": "~0.14.4" - }, - "devDependencies": { - "@angular-devkit/build-angular": "^17.3.8", - "@angular/cli": "^17.3.8", - "@angular/compiler-cli": "^17.3.9", - "@types/jasmine": "~5.1.4", - "jasmine-core": "~5.1.2", - "karma": "~6.4.3", - "karma-chrome-launcher": "~3.2.0", - "karma-coverage": "~2.2.1", - "karma-jasmine": "~5.1.0", - "karma-jasmine-html-reporter": "~2.1.0", - "tslib": "^2.6.2", - "typescript": "5.4.5" - } -} diff --git a/examples/angular/basic/src/app/app.component.html b/examples/angular/basic/src/app/app.component.html deleted file mode 100644 index 68c81953a6..0000000000 --- a/examples/angular/basic/src/app/app.component.html +++ /dev/null @@ -1,66 +0,0 @@ -
- - - @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { - - @for (header of headerGroup.headers; track header.id) { - @if (!header.isPlaceholder) { - - } - } - - } - - - @for (row of table.getRowModel().rows; track row.id) { - - @for (cell of row.getVisibleCells(); track cell.id) { - - } - - } - - - @for (footerGroup of table.getFooterGroups(); track footerGroup.id) { - - @for (footer of footerGroup.headers; track footer.id) { - - } - - } - -
- -
-
-
- -
-
-
- - {{ footer }} - -
- -
- -
diff --git a/examples/angular/basic/src/app/app.component.ts b/examples/angular/basic/src/app/app.component.ts deleted file mode 100644 index 29d0edb180..0000000000 --- a/examples/angular/basic/src/app/app.component.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { ChangeDetectionStrategy, Component, signal } from '@angular/core' -import { RouterOutlet } from '@angular/router' -import { - ColumnDef, - createAngularTable, - FlexRenderDirective, - getCoreRowModel, -} from '@tanstack/angular-table' - -type Person = { - firstName: string - lastName: string - age: number - visits: number - status: string - progress: number -} - -const defaultData: Person[] = [ - { - firstName: 'tanner', - lastName: 'linsley', - age: 24, - visits: 100, - status: 'In Relationship', - progress: 50, - }, - { - firstName: 'tandy', - lastName: 'miller', - age: 40, - visits: 40, - status: 'Single', - progress: 80, - }, - { - firstName: 'joe', - lastName: 'dirte', - age: 45, - visits: 20, - status: 'Complicated', - progress: 10, - }, -] - -const defaultColumns: ColumnDef[] = [ - { - accessorKey: 'firstName', - cell: info => info.getValue(), - footer: info => info.column.id, - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - cell: info => `${info.getValue()}`, - header: () => `Last Name`, - footer: info => info.column.id, - }, - { - accessorKey: 'age', - header: () => 'Age', - footer: info => info.column.id, - }, - { - accessorKey: 'visits', - header: () => `Visits`, - footer: info => info.column.id, - }, - { - accessorKey: 'status', - header: 'Status', - footer: info => info.column.id, - }, - { - accessorKey: 'progress', - header: 'Profile Progress', - footer: info => info.column.id, - }, -] - -@Component({ - selector: 'app-root', - standalone: true, - imports: [RouterOutlet, FlexRenderDirective], - templateUrl: './app.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AppComponent { - data = signal(defaultData) - - table = createAngularTable(() => ({ - data: this.data(), - columns: defaultColumns, - getCoreRowModel: getCoreRowModel(), - debugTable: true, - })) - - rerender() { - this.data.set([...defaultData.sort(() => -1)]) - } -} diff --git a/examples/angular/basic/src/app/app.config.ts b/examples/angular/basic/src/app/app.config.ts deleted file mode 100644 index bd9327036d..0000000000 --- a/examples/angular/basic/src/app/app.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ApplicationConfig } from '@angular/core' -import { provideRouter } from '@angular/router' - -import { routes } from './app.routes' - -export const appConfig: ApplicationConfig = { - providers: [provideRouter(routes)], -} diff --git a/examples/angular/basic/src/app/app.routes.ts b/examples/angular/basic/src/app/app.routes.ts deleted file mode 100644 index 246771c88a..0000000000 --- a/examples/angular/basic/src/app/app.routes.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Routes } from '@angular/router' - -export const routes: Routes = [] diff --git a/examples/angular/basic/src/assets/.gitkeep b/examples/angular/basic/src/assets/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/examples/angular/basic/src/index.html b/examples/angular/basic/src/index.html deleted file mode 100644 index a4bb987648..0000000000 --- a/examples/angular/basic/src/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - Basic - - - - - - - - - diff --git a/examples/angular/basic/src/main.ts b/examples/angular/basic/src/main.ts deleted file mode 100644 index 0c3b92057c..0000000000 --- a/examples/angular/basic/src/main.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { bootstrapApplication } from '@angular/platform-browser' -import { appConfig } from './app/app.config' -import { AppComponent } from './app/app.component' - -bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err)) diff --git a/examples/angular/basic/src/styles.scss b/examples/angular/basic/src/styles.scss deleted file mode 100644 index cda3113f7d..0000000000 --- a/examples/angular/basic/src/styles.scss +++ /dev/null @@ -1,32 +0,0 @@ -html { - font-family: sans-serif; - font-size: 14px; -} - -table { - border: 1px solid lightgray; -} - -tbody { - border-bottom: 1px solid lightgray; -} - -th { - border-bottom: 1px solid lightgray; - border-right: 1px solid lightgray; - padding: 2px 4px; -} - -tfoot { - color: gray; -} - -tfoot th { - font-weight: normal; -} - -.pagination-actions { - margin: 10px; - display: flex; - gap: 10px; -} diff --git a/examples/angular/basic/tsconfig.app.json b/examples/angular/basic/tsconfig.app.json deleted file mode 100644 index 84f1f992d2..0000000000 --- a/examples/angular/basic/tsconfig.app.json +++ /dev/null @@ -1,10 +0,0 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/app", - "types": [] - }, - "files": ["src/main.ts"], - "include": ["src/**/*.d.ts"] -} diff --git a/examples/angular/basic/tsconfig.json b/examples/angular/basic/tsconfig.json deleted file mode 100644 index b58d3efc71..0000000000 --- a/examples/angular/basic/tsconfig.json +++ /dev/null @@ -1,31 +0,0 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ -{ - "compileOnSave": false, - "compilerOptions": { - "baseUrl": "src", - "outDir": "./dist/out-tsc", - "forceConsistentCasingInFileNames": true, - "strict": true, - "noImplicitOverride": true, - "noPropertyAccessFromIndexSignature": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "skipLibCheck": true, - "esModuleInterop": true, - "sourceMap": true, - "declaration": false, - "experimentalDecorators": true, - "moduleResolution": "node", - "importHelpers": true, - "target": "ES2022", - "module": "ES2022", - "useDefineForClassFields": false, - "lib": ["ES2022", "dom"] - }, - "angularCompilerOptions": { - "enableI18nLegacyMessageIdFormat": false, - "strictInjectionParameters": true, - "strictInputAccessModifiers": true, - "strictTemplates": true - } -} diff --git a/examples/angular/column-ordering/.devcontainer/devcontainer.json b/examples/angular/column-ordering/.devcontainer/devcontainer.json deleted file mode 100644 index 36f47d8762..0000000000 --- a/examples/angular/column-ordering/.devcontainer/devcontainer.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "Node.js", - "image": "mcr.microsoft.com/devcontainers/javascript-node:18" -} diff --git a/examples/angular/column-ordering/.editorconfig b/examples/angular/column-ordering/.editorconfig deleted file mode 100644 index 59d9a3a3e7..0000000000 --- a/examples/angular/column-ordering/.editorconfig +++ /dev/null @@ -1,16 +0,0 @@ -# Editor configuration, see https://editorconfig.org -root = true - -[*] -charset = utf-8 -indent_style = space -indent_size = 2 -insert_final_newline = true -trim_trailing_whitespace = true - -[*.ts] -quote_type = single - -[*.md] -max_line_length = off -trim_trailing_whitespace = false diff --git a/examples/angular/column-ordering/.gitignore b/examples/angular/column-ordering/.gitignore index 0711527ef9..854acd5fc0 100644 --- a/examples/angular/column-ordering/.gitignore +++ b/examples/angular/column-ordering/.gitignore @@ -1,4 +1,4 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. # Compiled output /dist @@ -26,6 +26,7 @@ yarn-error.log !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json +!.vscode/mcp.json .history/* # Miscellaneous @@ -36,6 +37,7 @@ yarn-error.log /libpeerconnection.log testem.log /typings +__screenshots__/ # System files .DS_Store diff --git a/examples/angular/column-ordering/README.md b/examples/angular/column-ordering/README.md index 5da97a87d1..7ba2b2ea74 100644 --- a/examples/angular/column-ordering/README.md +++ b/examples/angular/column-ordering/README.md @@ -1,27 +1,59 @@ -# Basic +# CustomPlugin -This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.1.2. +This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.1.0. ## Development server -Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. +To start a local development server, run: + +```bash +ng serve +``` + +Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files. ## Code scaffolding -Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. +Angular CLI includes powerful code scaffolding tools. To generate a new component, run: + +```bash +ng generate component component-name +``` + +For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run: + +```bash +ng generate --help +``` -## Build +## Building -Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. +To build the project run: + +```bash +ng build +``` + +This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed. ## Running unit tests -Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). +To execute unit tests with the [Vitest](https://vitest.dev/) test runner, use the following command: + +```bash +ng test +``` ## Running end-to-end tests -Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. +For end-to-end (e2e) testing, run: + +```bash +ng e2e +``` + +Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs. -## Further help +## Additional Resources -To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. +For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. diff --git a/examples/angular/column-ordering/angular.json b/examples/angular/column-ordering/angular.json index 93bf48f9d1..e193fc5da5 100644 --- a/examples/angular/column-ordering/angular.json +++ b/examples/angular/column-ordering/angular.json @@ -1,18 +1,43 @@ { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, + "cli": { + "packageManager": "pnpm", + "analytics": false, + "cache": { + "enabled": false + } + }, "newProjectRoot": "projects", "projects": { - "basic": { - "cli": { - "cache": { - "enabled": false - } - }, + "column-ordering": { "projectType": "application", "schematics": { "@schematics/angular:component": { - "style": "scss" + "inlineTemplate": true, + "inlineStyle": true, + "skipTests": true + }, + "@schematics/angular:class": { + "skipTests": true + }, + "@schematics/angular:directive": { + "skipTests": true + }, + "@schematics/angular:guard": { + "skipTests": true + }, + "@schematics/angular:interceptor": { + "skipTests": true + }, + "@schematics/angular:pipe": { + "skipTests": true + }, + "@schematics/angular:resolver": { + "skipTests": true + }, + "@schematics/angular:service": { + "skipTests": true } }, "root": "", @@ -20,21 +45,32 @@ "prefix": "app", "architect": { "build": { - "builder": "@angular-devkit/build-angular:application", + "builder": "@angular/build:application", "options": { - "outputPath": "dist/column-ordering", - "index": "src/index.html", "browser": "src/main.ts", - "polyfills": ["zone.js"], "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": ["src/favicon.ico", "src/assets"], - "styles": ["src/styles.scss"], - "scripts": [] + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": ["src/styles.css"] }, "configurations": { "production": { - "budgets": [], + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kB", + "maximumError": "8kB" + } + ], "outputHashing": "all" }, "development": { @@ -46,38 +82,18 @@ "defaultConfiguration": "production" }, "serve": { - "builder": "@angular-devkit/build-angular:dev-server", + "builder": "@angular/build:dev-server", "configurations": { "production": { - "buildTarget": "basic:build:production" + "buildTarget": "column-ordering:build:production" }, "development": { - "buildTarget": "basic:build:development" + "buildTarget": "column-ordering:build:development" } }, "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "buildTarget": "basic:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "polyfills": ["zone.js", "zone.js/testing"], - "tsConfig": "tsconfig.spec.json", - "inlineStyleLanguage": "scss", - "assets": ["src/favicon.ico", "src/assets"], - "styles": ["src/styles.scss"], - "scripts": [] - } } } } - }, - "cli": { - "analytics": false } } diff --git a/examples/angular/column-ordering/package.json b/examples/angular/column-ordering/package.json index 0be40f4c65..1d3951da05 100644 --- a/examples/angular/column-ordering/package.json +++ b/examples/angular/column-ordering/package.json @@ -1,39 +1,31 @@ { - "name": "tanstack-table-example-angular-column-ordering", - "version": "0.0.0", + "name": "tanstack-angular-table-example-column-ordering", "scripts": { "ng": "ng", - "start": "ng serve", + "start": "ng serve --port 5173", + "dev": "ng serve --port 5173", "build": "ng build", - "watch": "ng build --watch --configuration development", - "test": "ng test" + "lint": "eslint ./src", + "watch": "ng build --watch --configuration development" }, "private": true, + "packageManager": "pnpm@11.0.9", "dependencies": { - "@angular/animations": "^17.3.9", - "@angular/common": "^17.3.9", - "@angular/compiler": "^17.3.9", - "@angular/core": "^17.3.9", - "@angular/forms": "^17.3.9", - "@angular/platform-browser": "^17.3.9", - "@angular/platform-browser-dynamic": "^17.3.9", - "@faker-js/faker": "^8.4.1", - "@tanstack/angular-table": "^8.20.5", - "rxjs": "~7.8.1", - "zone.js": "~0.14.4" + "@angular/common": "^21.2.12", + "@angular/compiler": "^21.2.12", + "@angular/core": "^21.2.12", + "@angular/forms": "^21.2.12", + "@angular/platform-browser": "^21.2.12", + "@angular/router": "^21.2.12", + "@faker-js/faker": "^10.4.0", + "@tanstack/angular-table": "^9.0.0-alpha.45", + "rxjs": "~7.8.2", + "tslib": "^2.8.1" }, "devDependencies": { - "@angular-devkit/build-angular": "^17.3.8", - "@angular/cli": "^17.3.8", - "@angular/compiler-cli": "^17.3.9", - "@types/jasmine": "~5.1.4", - "jasmine-core": "~5.1.2", - "karma": "~6.4.3", - "karma-chrome-launcher": "~3.2.0", - "karma-coverage": "~2.2.1", - "karma-jasmine": "~5.1.0", - "karma-jasmine-html-reporter": "~2.1.0", - "tslib": "^2.6.2", - "typescript": "5.4.5" + "@angular/build": "^21.2.10", + "@angular/cli": "^21.2.10", + "@angular/compiler-cli": "^21.2.12", + "typescript": "6.0.3" } } diff --git a/examples/angular/column-pinning-sticky/src/favicon.ico b/examples/angular/column-ordering/public/favicon.ico similarity index 100% rename from examples/angular/column-pinning-sticky/src/favicon.ico rename to examples/angular/column-ordering/public/favicon.ico diff --git a/examples/angular/column-ordering/src/app/app.component.html b/examples/angular/column-ordering/src/app/app.component.html deleted file mode 100644 index fca57e650a..0000000000 --- a/examples/angular/column-ordering/src/app/app.component.html +++ /dev/null @@ -1,102 +0,0 @@ -
-
-
- -
- - @for (column of table.getAllLeafColumns(); track column.id) { -
- -
- } -
- -
-
- - -
- - - - @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { - - @for (header of headerGroup.headers; track header.id) { - - } - - } - - - @for (row of table.getRowModel().rows; track row.id) { - - @for (cell of row.getVisibleCells(); track cell.id) { - - } - - } - - - @for (footerGroup of table.getFooterGroups(); track footerGroup.id) { - - @for (header of footerGroup.headers; track header.id) { - - } - - } - -
- @if (!header.isPlaceholder) { - - {{ header }} - - } -
- - {{ cell }} - -
- @if (!header.isPlaceholder) { - - {{ header }} - - } -
- -
-
{{ stringifiedColumnOrdering() }}
-
diff --git a/examples/angular/column-ordering/src/app/app.component.ts b/examples/angular/column-ordering/src/app/app.component.ts deleted file mode 100644 index 2f447c6f61..0000000000 --- a/examples/angular/column-ordering/src/app/app.component.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - computed, - signal, -} from '@angular/core' -import { - ColumnDef, - type ColumnOrderState, - createAngularTable, - FlexRenderDirective, - getCoreRowModel, - type VisibilityState, -} from '@tanstack/angular-table' -import { makeData, type Person } from './makeData' -import { faker } from '@faker-js/faker' - -const defaultColumns: ColumnDef[] = [ - { - header: 'Name', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'firstName', - cell: info => info.getValue(), - footer: props => props.column.id, - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - cell: info => info.getValue(), - header: () => 'Last Name', - footer: props => props.column.id, - }, - ], - }, - { - header: 'Info', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'age', - header: () => 'Age', - footer: props => props.column.id, - }, - { - header: 'More Info', - columns: [ - { - accessorKey: 'visits', - header: () => 'Visits', - footer: props => props.column.id, - }, - { - accessorKey: 'status', - header: 'Status', - footer: props => props.column.id, - }, - { - accessorKey: 'progress', - header: 'Profile Progress', - footer: props => props.column.id, - }, - ], - }, - ], - }, -] - -@Component({ - selector: 'app-root', - standalone: true, - imports: [FlexRenderDirective], - templateUrl: './app.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AppComponent { - readonly data = signal(makeData(20)) - readonly columnVisibility = signal({}) - readonly columnOrder = signal([]) - - readonly table = createAngularTable(() => ({ - data: this.data(), - columns: defaultColumns, - state: { - columnOrder: this.columnOrder(), - columnVisibility: this.columnVisibility(), - }, - getCoreRowModel: getCoreRowModel(), - onColumnVisibilityChange: updaterOrValue => { - typeof updaterOrValue === 'function' - ? this.columnVisibility.update(updaterOrValue) - : this.columnVisibility.set(updaterOrValue) - }, - onColumnOrderChange: updaterOrValue => { - typeof updaterOrValue === 'function' - ? this.columnOrder.update(updaterOrValue) - : this.columnOrder.set(updaterOrValue) - }, - debugTable: true, - debugHeaders: true, - debugColumns: true, - })) - - readonly stringifiedColumnOrdering = computed(() => { - return JSON.stringify(this.table.getState().columnOrder) - }) - - randomizeColumns() { - this.table.setColumnOrder( - faker.helpers.shuffle(this.table.getAllLeafColumns().map(d => d.id)) - ) - } - - rerender() { - this.data.set([...makeData(20)]) - } -} diff --git a/examples/angular/column-ordering/src/app/app.config.ts b/examples/angular/column-ordering/src/app/app.config.ts index f27099f33c..cbb47d366c 100644 --- a/examples/angular/column-ordering/src/app/app.config.ts +++ b/examples/angular/column-ordering/src/app/app.config.ts @@ -1,5 +1,6 @@ -import { ApplicationConfig } from '@angular/core' +import { provideBrowserGlobalErrorListeners } from '@angular/core' +import type { ApplicationConfig } from '@angular/core' export const appConfig: ApplicationConfig = { - providers: [], + providers: [provideBrowserGlobalErrorListeners()], } diff --git a/examples/angular/column-ordering/src/app/app.html b/examples/angular/column-ordering/src/app/app.html new file mode 100644 index 0000000000..d2809472ba --- /dev/null +++ b/examples/angular/column-ordering/src/app/app.html @@ -0,0 +1,97 @@ +
+
+
+ +
+ + @for (column of table.getAllLeafColumns(); track column.id) { +
+ +
+ } +
+ +
+
+ + + +
+ + + + @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { + + @for (header of headerGroup.headers; track header.id) { + + } + + } + + + @for (row of table.getRowModel().rows; track row.id) { + + @for (cell of row.getVisibleCells(); track cell.id) { + + } + + } + + + @for (footerGroup of table.getFooterGroups(); track footerGroup.id) { + + @for (header of footerGroup.headers; track header.id) { + + } + + } + +
+ @if (!header.isPlaceholder) { + + {{ header }} + + } +
+ + {{ cell }} + +
+ @if (!header.isPlaceholder) { + + {{ header }} + + } +
+ +
+
{{ stringifiedColumnOrdering() }}
+
diff --git a/examples/angular/column-ordering/src/app/app.ts b/examples/angular/column-ordering/src/app/app.ts new file mode 100644 index 0000000000..adcd415740 --- /dev/null +++ b/examples/angular/column-ordering/src/app/app.ts @@ -0,0 +1,121 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + signal, +} from '@angular/core' +import { + FlexRender, + columnOrderingFeature, + columnVisibilityFeature, + createColumnHelper, + injectTable, + tableFeatures, +} from '@tanstack/angular-table' +import { faker } from '@faker-js/faker' +import { makeData } from './makeData' +import type { Person } from './makeData' +import type { + ColumnOrderState, + ColumnVisibilityState, +} from '@tanstack/angular-table' + +const _features = tableFeatures({ + columnVisibilityFeature, + columnOrderingFeature, +}) + +const columnHelper = createColumnHelper() + +const defaultColumns = columnHelper.columns([ + columnHelper.group({ + header: 'Name', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor('lastName', { + cell: (info) => info.getValue(), + header: () => 'Last Name', + footer: (props) => props.column.id, + }), + ]), + }), + columnHelper.group({ + header: 'Info', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + }), + columnHelper.group({ + header: 'More Info', + columns: columnHelper.columns([ + columnHelper.accessor('visits', { + header: () => 'Visits', + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), + ]), + }), + ]), + }), +]) + +@Component({ + selector: 'app-root', + imports: [FlexRender], + templateUrl: './app.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class App { + readonly data = signal>(makeData(20)) + readonly columnVisibility = signal({}) + readonly columnOrder = signal([]) + + readonly table = injectTable(() => ({ + _features, + data: this.data(), + columns: defaultColumns, + state: { + columnOrder: this.columnOrder(), + columnVisibility: this.columnVisibility(), + }, + onColumnVisibilityChange: (updaterOrValue) => { + typeof updaterOrValue === 'function' + ? this.columnVisibility.update(updaterOrValue) + : this.columnVisibility.set(updaterOrValue) + }, + onColumnOrderChange: (updaterOrValue) => { + typeof updaterOrValue === 'function' + ? this.columnOrder.update(updaterOrValue) + : this.columnOrder.set(updaterOrValue) + }, + debugTable: true, + debugHeaders: true, + debugColumns: true, + })) + + readonly stringifiedColumnOrdering = computed(() => { + return JSON.stringify(this.table.store.state.columnOrder) + }) + + randomizeColumns() { + this.table.setColumnOrder( + faker.helpers.shuffle(this.table.getAllLeafColumns().map((d) => d.id)), + ) + } + + refreshData = () => this.data.set(makeData(20)) + stressTest = () => this.data.set(makeData(1_000)) +} diff --git a/examples/angular/column-ordering/src/app/makeData.ts b/examples/angular/column-ordering/src/app/makeData.ts index 331dd1eb19..b9fb014aba 100644 --- a/examples/angular/column-ordering/src/app/makeData.ts +++ b/examples/angular/column-ordering/src/app/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,13 +29,13 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] return range(len).map((d): Person => { return { ...newPerson(), diff --git a/examples/angular/column-ordering/src/assets/.gitkeep b/examples/angular/column-ordering/src/assets/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/examples/angular/column-ordering/src/index.html b/examples/angular/column-ordering/src/index.html index a4bb987648..1b3f817b9e 100644 --- a/examples/angular/column-ordering/src/index.html +++ b/examples/angular/column-ordering/src/index.html @@ -6,7 +6,6 @@ - diff --git a/examples/angular/column-ordering/src/main.ts b/examples/angular/column-ordering/src/main.ts index 0c3b92057c..8192dca694 100644 --- a/examples/angular/column-ordering/src/main.ts +++ b/examples/angular/column-ordering/src/main.ts @@ -1,5 +1,5 @@ import { bootstrapApplication } from '@angular/platform-browser' import { appConfig } from './app/app.config' -import { AppComponent } from './app/app.component' +import { App } from './app/app' -bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err)) +bootstrapApplication(App, appConfig).catch((err) => console.error(err)) diff --git a/examples/angular/column-ordering/src/styles.css b/examples/angular/column-ordering/src/styles.css new file mode 100644 index 0000000000..efd0b26417 --- /dev/null +++ b/examples/angular/column-ordering/src/styles.css @@ -0,0 +1,374 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td { + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td:last-child { + border-right: 0; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/angular/column-ordering/src/styles.scss b/examples/angular/column-ordering/src/styles.scss deleted file mode 100644 index 93034cdd1b..0000000000 --- a/examples/angular/column-ordering/src/styles.scss +++ /dev/null @@ -1,35 +0,0 @@ -html { - font-family: sans-serif; - font-size: 14px; -} - -table { - border: 1px solid lightgray; -} - -tbody { - border-bottom: 1px solid lightgray; -} - -th { - border-bottom: 1px solid lightgray; - border-right: 1px solid lightgray; - padding: 2px 4px; -} - -td { - border-right: 1px solid lightgray; - padding: 2px 4px; -} - -td:last-child { - border-right: 0; -} - -tfoot { - color: gray; -} - -tfoot th { - font-weight: normal; -} diff --git a/examples/angular/column-ordering/tsconfig.app.json b/examples/angular/column-ordering/tsconfig.app.json index 84f1f992d2..a0dcc37c60 100644 --- a/examples/angular/column-ordering/tsconfig.app.json +++ b/examples/angular/column-ordering/tsconfig.app.json @@ -1,10 +1,11 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/app", "types": [] }, - "files": ["src/main.ts"], - "include": ["src/**/*.d.ts"] + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] } diff --git a/examples/angular/column-ordering/tsconfig.json b/examples/angular/column-ordering/tsconfig.json index b58d3efc71..32789cdc2b 100644 --- a/examples/angular/column-ordering/tsconfig.json +++ b/examples/angular/column-ordering/tsconfig.json @@ -1,31 +1,23 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ { "compileOnSave": false, "compilerOptions": { - "baseUrl": "src", - "outDir": "./dist/out-tsc", - "forceConsistentCasingInFileNames": true, "strict": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "skipLibCheck": true, - "esModuleInterop": true, - "sourceMap": true, - "declaration": false, + "isolatedModules": true, "experimentalDecorators": true, - "moduleResolution": "node", "importHelpers": true, "target": "ES2022", - "module": "ES2022", - "useDefineForClassFields": false, - "lib": ["ES2022", "dom"] + "module": "preserve" }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": true, "strictInputAccessModifiers": true, "strictTemplates": true - } + }, + "include": ["**/*.ts", "vite.config.js", "vite.config.ts"] } diff --git a/examples/angular/column-ordering/tsconfig.spec.json b/examples/angular/column-ordering/tsconfig.spec.json deleted file mode 100644 index 47e3dd7551..0000000000 --- a/examples/angular/column-ordering/tsconfig.spec.json +++ /dev/null @@ -1,9 +0,0 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/spec", - "types": ["jasmine"] - }, - "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] -} diff --git a/examples/angular/column-pinning-sticky/.devcontainer/devcontainer.json b/examples/angular/column-pinning-sticky/.devcontainer/devcontainer.json deleted file mode 100644 index 36f47d8762..0000000000 --- a/examples/angular/column-pinning-sticky/.devcontainer/devcontainer.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "Node.js", - "image": "mcr.microsoft.com/devcontainers/javascript-node:18" -} diff --git a/examples/angular/column-pinning-sticky/.editorconfig b/examples/angular/column-pinning-sticky/.editorconfig deleted file mode 100644 index 59d9a3a3e7..0000000000 --- a/examples/angular/column-pinning-sticky/.editorconfig +++ /dev/null @@ -1,16 +0,0 @@ -# Editor configuration, see https://editorconfig.org -root = true - -[*] -charset = utf-8 -indent_style = space -indent_size = 2 -insert_final_newline = true -trim_trailing_whitespace = true - -[*.ts] -quote_type = single - -[*.md] -max_line_length = off -trim_trailing_whitespace = false diff --git a/examples/angular/column-pinning-sticky/.gitignore b/examples/angular/column-pinning-sticky/.gitignore index 0711527ef9..854acd5fc0 100644 --- a/examples/angular/column-pinning-sticky/.gitignore +++ b/examples/angular/column-pinning-sticky/.gitignore @@ -1,4 +1,4 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. # Compiled output /dist @@ -26,6 +26,7 @@ yarn-error.log !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json +!.vscode/mcp.json .history/* # Miscellaneous @@ -36,6 +37,7 @@ yarn-error.log /libpeerconnection.log testem.log /typings +__screenshots__/ # System files .DS_Store diff --git a/examples/angular/column-pinning-sticky/README.md b/examples/angular/column-pinning-sticky/README.md index 5da97a87d1..ac9222e534 100644 --- a/examples/angular/column-pinning-sticky/README.md +++ b/examples/angular/column-pinning-sticky/README.md @@ -1,27 +1,59 @@ -# Basic +# Column Pinning Sticky -This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.1.2. +This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.1.0. ## Development server -Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. +To start a local development server, run: + +```bash +ng serve +``` + +Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files. ## Code scaffolding -Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. +Angular CLI includes powerful code scaffolding tools. To generate a new component, run: + +```bash +ng generate component component-name +``` + +For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run: + +```bash +ng generate --help +``` -## Build +## Building -Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. +To build the project run: + +```bash +ng build +``` + +This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed. ## Running unit tests -Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). +To execute unit tests with the [Vitest](https://vitest.dev/) test runner, use the following command: + +```bash +ng test +``` ## Running end-to-end tests -Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. +For end-to-end (e2e) testing, run: + +```bash +ng e2e +``` + +Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs. -## Further help +## Additional Resources -To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. +For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. diff --git a/examples/angular/column-pinning-sticky/angular.json b/examples/angular/column-pinning-sticky/angular.json index c6cd127977..1afab3f56f 100644 --- a/examples/angular/column-pinning-sticky/angular.json +++ b/examples/angular/column-pinning-sticky/angular.json @@ -1,18 +1,43 @@ { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, + "cli": { + "packageManager": "pnpm", + "analytics": false, + "cache": { + "enabled": false + } + }, "newProjectRoot": "projects", "projects": { - "basic": { - "cli": { - "cache": { - "enabled": false - } - }, + "column-pinning-sticky": { "projectType": "application", "schematics": { "@schematics/angular:component": { - "style": "scss" + "inlineTemplate": true, + "inlineStyle": true, + "skipTests": true + }, + "@schematics/angular:class": { + "skipTests": true + }, + "@schematics/angular:directive": { + "skipTests": true + }, + "@schematics/angular:guard": { + "skipTests": true + }, + "@schematics/angular:interceptor": { + "skipTests": true + }, + "@schematics/angular:pipe": { + "skipTests": true + }, + "@schematics/angular:resolver": { + "skipTests": true + }, + "@schematics/angular:service": { + "skipTests": true } }, "root": "", @@ -20,21 +45,32 @@ "prefix": "app", "architect": { "build": { - "builder": "@angular-devkit/build-angular:application", + "builder": "@angular/build:application", "options": { - "outputPath": "dist/column-pinning-sticky", - "index": "src/index.html", "browser": "src/main.ts", - "polyfills": ["zone.js"], "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": ["src/favicon.ico", "src/assets"], - "styles": ["src/styles.scss"], - "scripts": [] + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": ["src/styles.css"] }, "configurations": { "production": { - "budgets": [], + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kB", + "maximumError": "8kB" + } + ], "outputHashing": "all" }, "development": { @@ -46,38 +82,18 @@ "defaultConfiguration": "production" }, "serve": { - "builder": "@angular-devkit/build-angular:dev-server", + "builder": "@angular/build:dev-server", "configurations": { "production": { - "buildTarget": "basic:build:production" + "buildTarget": "column-pinning-sticky:build:production" }, "development": { - "buildTarget": "basic:build:development" + "buildTarget": "column-pinning-sticky:build:development" } }, "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "buildTarget": "basic:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "polyfills": ["zone.js", "zone.js/testing"], - "tsConfig": "tsconfig.spec.json", - "inlineStyleLanguage": "scss", - "assets": ["src/favicon.ico", "src/assets"], - "styles": ["src/styles.scss"], - "scripts": [] - } } } } - }, - "cli": { - "analytics": false } } diff --git a/examples/angular/column-pinning-sticky/package.json b/examples/angular/column-pinning-sticky/package.json index ed69d5b3ea..1382645b5f 100644 --- a/examples/angular/column-pinning-sticky/package.json +++ b/examples/angular/column-pinning-sticky/package.json @@ -1,39 +1,31 @@ { - "name": "tanstack-table-example-angular-column-pinning-sticky", - "version": "0.0.0", + "name": "tanstack-angular-table-example-column-pinning-sticky", "scripts": { "ng": "ng", - "start": "ng serve", + "start": "ng serve --port 5173", + "dev": "ng serve --port 5173", "build": "ng build", - "watch": "ng build --watch --configuration development", - "test": "ng test" + "lint": "eslint ./src", + "watch": "ng build --watch --configuration development" }, "private": true, + "packageManager": "pnpm@11.0.9", "dependencies": { - "@angular/animations": "^17.3.9", - "@angular/common": "^17.3.9", - "@angular/compiler": "^17.3.9", - "@angular/core": "^17.3.9", - "@angular/forms": "^17.3.9", - "@angular/platform-browser": "^17.3.9", - "@angular/platform-browser-dynamic": "^17.3.9", - "@faker-js/faker": "^8.4.1", - "@tanstack/angular-table": "^8.20.5", - "rxjs": "~7.8.1", - "zone.js": "~0.14.4" + "@angular/common": "^21.2.12", + "@angular/compiler": "^21.2.12", + "@angular/core": "^21.2.12", + "@angular/forms": "^21.2.12", + "@angular/platform-browser": "^21.2.12", + "@angular/router": "^21.2.12", + "@faker-js/faker": "^10.4.0", + "@tanstack/angular-table": "^9.0.0-alpha.45", + "rxjs": "~7.8.2", + "tslib": "^2.8.1" }, "devDependencies": { - "@angular-devkit/build-angular": "^17.3.8", - "@angular/cli": "^17.3.8", - "@angular/compiler-cli": "^17.3.9", - "@types/jasmine": "~5.1.4", - "jasmine-core": "~5.1.2", - "karma": "~6.4.3", - "karma-chrome-launcher": "~3.2.0", - "karma-coverage": "~2.2.1", - "karma-jasmine": "~5.1.0", - "karma-jasmine-html-reporter": "~2.1.0", - "tslib": "^2.6.2", - "typescript": "5.4.5" + "@angular/build": "^21.2.10", + "@angular/cli": "^21.2.10", + "@angular/compiler-cli": "^21.2.12", + "typescript": "6.0.3" } } diff --git a/examples/angular/column-pinning/src/favicon.ico b/examples/angular/column-pinning-sticky/public/favicon.ico similarity index 100% rename from examples/angular/column-pinning/src/favicon.ico rename to examples/angular/column-pinning-sticky/public/favicon.ico diff --git a/examples/angular/column-pinning-sticky/src/app/app.component.html b/examples/angular/column-pinning-sticky/src/app/app.component.html deleted file mode 100644 index 141fce97c0..0000000000 --- a/examples/angular/column-pinning-sticky/src/app/app.component.html +++ /dev/null @@ -1,137 +0,0 @@ -
-
-
- -
- - @for (column of table.getAllLeafColumns(); track column.id) { -
- -
- } -
- -
- -
- - -
-
- -
- - - @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { - - @for (header of headerGroup.headers; track header.id) { - - - } - - } - - - @for (row of table.getRowModel().rows; track row.id) { - - @for (cell of row.getVisibleCells(); track cell.id) { - - } - - } - -
-
- @if (!header.isPlaceholder) { - - {{ headerValue }} - - } - - {{ - header.column.getIndex( - header.column.getIsPinned() || 'center' - ) - }} -
- - @if (!header.isPlaceholder && header.column.getCanPin()) { -
- @if (header.column.getIsPinned() !== 'left') { - - } - - @if (header.column.getIsPinned()) { - - } - - @if (header.column.getIsPinned() !== 'right') { - - } -
- } - - -
-
- - {{ cellValue }} - -
-
-
- -
-
{{ stringifiedColumnPinning() }}
diff --git a/examples/angular/column-pinning-sticky/src/app/app.component.ts b/examples/angular/column-pinning-sticky/src/app/app.component.ts deleted file mode 100644 index c9fe3ce9e6..0000000000 --- a/examples/angular/column-pinning-sticky/src/app/app.component.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - computed, - signal, -} from '@angular/core' -import { - Column, - ColumnDef, - type ColumnOrderState, - type ColumnPinningState, - createAngularTable, - FlexRenderDirective, - getCoreRowModel, - type VisibilityState, -} from '@tanstack/angular-table' -import { makeData } from './makeData' -import { faker } from '@faker-js/faker' -import { NgStyle, NgTemplateOutlet, SlicePipe } from '@angular/common' - -type Person = { - firstName: string - lastName: string - age: number - visits: number - status: string - progress: number -} - -const defaultColumns: ColumnDef[] = [ - { - accessorKey: 'firstName', - id: 'firstName', - header: 'First Name', - cell: info => info.getValue(), - footer: props => props.column.id, - size: 180, - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - cell: info => info.getValue(), - header: () => 'Last Name', - footer: props => props.column.id, - size: 180, - }, - { - accessorKey: 'age', - id: 'age', - header: 'Age', - footer: props => props.column.id, - size: 180, - }, - { - accessorKey: 'visits', - id: 'visits', - header: 'Visits', - footer: props => props.column.id, - size: 180, - }, - { - accessorKey: 'status', - id: 'status', - header: 'Status', - footer: props => props.column.id, - size: 180, - }, - { - accessorKey: 'progress', - id: 'progress', - header: 'Profile Progress', - footer: props => props.column.id, - size: 180, - }, -] - -@Component({ - selector: 'app-root', - standalone: true, - imports: [FlexRenderDirective, SlicePipe, NgTemplateOutlet, NgStyle], - templateUrl: './app.component.html', -}) -export class AppComponent { - readonly columns = signal([...defaultColumns]) - readonly data = signal(makeData(30)) - readonly columnVisibility = signal({}) - readonly columnOrder = signal([]) - readonly columnPinning = signal({}) - readonly split = signal(false) - - table = createAngularTable(() => ({ - data: this.data(), - columns: this.columns(), - getCoreRowModel: getCoreRowModel(), - debugTable: true, - debugHeaders: true, - debugColumns: true, - columnResizeMode: 'onChange', - })) - - stringifiedColumnPinning = computed(() => { - return JSON.stringify(this.table.getState().columnPinning) - }) - - readonly getCommonPinningStyles = ( - column: Column - ): Record => { - const isPinned = column.getIsPinned() - const isLastLeftPinnedColumn = - isPinned === 'left' && column.getIsLastColumn('left') - const isFirstRightPinnedColumn = - isPinned === 'right' && column.getIsFirstColumn('right') - - return { - boxShadow: isLastLeftPinnedColumn - ? '-4px 0 4px -4px gray inset' - : isFirstRightPinnedColumn - ? '4px 0 4px -4px gray inset' - : undefined, - left: isPinned === 'left' ? `${column.getStart('left')}px` : undefined, - right: isPinned === 'right' ? `${column.getAfter('right')}px` : undefined, - opacity: isPinned ? 0.95 : 1, - position: isPinned ? 'sticky' : 'relative', - width: `${column.getSize()}px`, - zIndex: isPinned ? 1 : 0, - } - } - - randomizeColumns() { - this.table.setColumnOrder( - faker.helpers.shuffle(this.table.getAllLeafColumns().map(d => d.id)) - ) - } - - rerender() { - this.data.set(makeData(5000)) - } -} diff --git a/examples/angular/column-pinning-sticky/src/app/app.config.ts b/examples/angular/column-pinning-sticky/src/app/app.config.ts index f27099f33c..cbb47d366c 100644 --- a/examples/angular/column-pinning-sticky/src/app/app.config.ts +++ b/examples/angular/column-pinning-sticky/src/app/app.config.ts @@ -1,5 +1,6 @@ -import { ApplicationConfig } from '@angular/core' +import { provideBrowserGlobalErrorListeners } from '@angular/core' +import type { ApplicationConfig } from '@angular/core' export const appConfig: ApplicationConfig = { - providers: [], + providers: [provideBrowserGlobalErrorListeners()], } diff --git a/examples/angular/column-pinning-sticky/src/app/app.html b/examples/angular/column-pinning-sticky/src/app/app.html new file mode 100644 index 0000000000..e961042585 --- /dev/null +++ b/examples/angular/column-pinning-sticky/src/app/app.html @@ -0,0 +1,110 @@ +
+
+
+ +
+ + @for (column of table.getAllLeafColumns(); track column.id) { +
+ +
+ } +
+ +
+ +
+ + + +
+
+ +
+ + + @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { + + @for (header of headerGroup.headers; track header.id) { + + + } + + } + + + @for (row of table.getRowModel().rows; track row.id) { + + @for (cell of row.getVisibleCells(); track cell.id) { + + } + + } + +
+
+ @if (!header.isPlaceholder) { + + {{ headerValue }} + + } + + {{ header.column.getIndex(header.column.getIsPinned() || 'center') }} +
+ + @if (!header.isPlaceholder && header.column.getCanPin()) { +
+ @if (header.column.getIsPinned() !== 'left') { + + } + + @if (header.column.getIsPinned()) { + + } + + @if (header.column.getIsPinned() !== 'right') { + + } +
+ } + + +
+
+ + {{ cellValue }} + +
+
+
+ +
+
{{ stringifiedColumnPinning() }}
diff --git a/examples/angular/column-pinning-sticky/src/app/app.ts b/examples/angular/column-pinning-sticky/src/app/app.ts new file mode 100644 index 0000000000..c001307ce2 --- /dev/null +++ b/examples/angular/column-pinning-sticky/src/app/app.ts @@ -0,0 +1,136 @@ +import { Component, computed, signal } from '@angular/core' +import { + FlexRender, + columnOrderingFeature, + columnPinningFeature, + columnResizingFeature, + columnSizingFeature, + columnVisibilityFeature, + createColumnHelper, + injectTable, + tableFeatures, +} from '@tanstack/angular-table' +import { faker } from '@faker-js/faker' +import { makeData } from './makeData' +import type { Person } from './makeData' +import type { + Column, + ColumnOrderState, + ColumnPinningState, + ColumnVisibilityState, +} from '@tanstack/angular-table' + +const _features = tableFeatures({ + columnOrderingFeature, + columnPinningFeature, + columnResizingFeature, + columnSizingFeature, + columnVisibilityFeature, +}) + +const columnHelper = createColumnHelper() + +const defaultColumns = columnHelper.columns([ + columnHelper.accessor('firstName', { + id: 'firstName', + header: 'First Name', + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + size: 180, + }), + columnHelper.accessor('lastName', { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => 'Last Name', + footer: (props) => props.column.id, + size: 180, + }), + columnHelper.accessor('age', { + id: 'age', + header: 'Age', + footer: (props) => props.column.id, + size: 180, + }), + columnHelper.accessor('visits', { + id: 'visits', + header: 'Visits', + footer: (props) => props.column.id, + size: 180, + }), + columnHelper.accessor('status', { + id: 'status', + header: 'Status', + footer: (props) => props.column.id, + size: 180, + }), + columnHelper.accessor('progress', { + id: 'progress', + header: 'Profile Progress', + footer: (props) => props.column.id, + size: 180, + }), +]) + +@Component({ + selector: 'app-root', + imports: [FlexRender], + templateUrl: './app.html', +}) +export class App { + readonly columns = signal([...defaultColumns]) + readonly data = signal>(makeData(20)) + readonly columnVisibility = signal({}) + readonly columnOrder = signal([]) + readonly columnPinning = signal({ + left: [], + right: [], + }) + readonly split = signal(false) + + table = injectTable(() => ({ + _features, + columns: this.columns(), + data: this.data(), + debugTable: true, + debugHeaders: true, + debugColumns: true, + columnResizeMode: 'onChange' as const, + })) + + stringifiedColumnPinning = computed(() => { + return JSON.stringify(this.table.store.state.columnPinning) + }) + + readonly getCommonPinningStyles = ( + column: Column, + ): Record => { + const isPinned = column.getIsPinned() + const isLastLeftPinnedColumn = + isPinned === 'left' && column.getIsLastColumn('left') + const isFirstRightPinnedColumn = + isPinned === 'right' && column.getIsFirstColumn('right') + + return { + boxShadow: isLastLeftPinnedColumn + ? '-4px 0 4px -4px gray inset' + : isFirstRightPinnedColumn + ? '4px 0 4px -4px gray inset' + : undefined, + left: isPinned === 'left' ? `${column.getStart('left')}px` : undefined, + right: isPinned === 'right' ? `${column.getAfter('right')}px` : undefined, + opacity: isPinned ? 0.95 : 1, + position: isPinned ? 'sticky' : 'relative', + width: `${column.getSize()}px`, + zIndex: isPinned ? 1 : 0, + } + } + + randomizeColumns() { + this.table.setColumnOrder( + faker.helpers.shuffle(this.table.getAllLeafColumns().map((d) => d.id)), + ) + } + + refreshData = () => this.data.set(makeData(20)) + stressTest = () => this.data.set(makeData(1_000)) +} diff --git a/examples/angular/column-pinning-sticky/src/app/makeData.ts b/examples/angular/column-pinning-sticky/src/app/makeData.ts index 331dd1eb19..b9fb014aba 100644 --- a/examples/angular/column-pinning-sticky/src/app/makeData.ts +++ b/examples/angular/column-pinning-sticky/src/app/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,13 +29,13 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] return range(len).map((d): Person => { return { ...newPerson(), diff --git a/examples/angular/column-pinning-sticky/src/assets/.gitkeep b/examples/angular/column-pinning-sticky/src/assets/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/examples/angular/column-pinning-sticky/src/index.html b/examples/angular/column-pinning-sticky/src/index.html index a4bb987648..17e4f36517 100644 --- a/examples/angular/column-pinning-sticky/src/index.html +++ b/examples/angular/column-pinning-sticky/src/index.html @@ -2,11 +2,10 @@ - Basic + Column Pinning Sticky - diff --git a/examples/angular/column-pinning-sticky/src/main.ts b/examples/angular/column-pinning-sticky/src/main.ts index 0c3b92057c..8192dca694 100644 --- a/examples/angular/column-pinning-sticky/src/main.ts +++ b/examples/angular/column-pinning-sticky/src/main.ts @@ -1,5 +1,5 @@ import { bootstrapApplication } from '@angular/platform-browser' import { appConfig } from './app/app.config' -import { AppComponent } from './app/app.component' +import { App } from './app/app' -bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err)) +bootstrapApplication(App, appConfig).catch((err) => console.error(err)) diff --git a/examples/angular/column-pinning-sticky/src/styles.css b/examples/angular/column-pinning-sticky/src/styles.css new file mode 100644 index 0000000000..5d1f3392a1 --- /dev/null +++ b/examples/angular/column-pinning-sticky/src/styles.css @@ -0,0 +1,389 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +.table-container { + border: 1px solid lightgray; + overflow-x: scroll; + width: 100%; + max-width: 960px; + position: relative; + border-collapse: collapse; + border-spacing: 0; +} + +table { + /* box-shadow and borders will not work with position: sticky otherwise */ + border-collapse: collapse; + border-spacing: 0; +} + +th { + background-color: lightgray; + border-bottom: 1px solid lightgray; + font-weight: bold; + height: 30px; + padding: 2px 4px; + position: relative; + text-align: center; +} + +td { + background-color: white; + padding: 2px 4px; +} + +.resizer { + background: rgba(0, 0, 0, 0.5); + cursor: col-resize; + height: 100%; + position: absolute; + right: 0; + top: 0; + touch-action: none; + user-select: none; + width: 5px; +} + +.resizer.isResizing { + background: blue; + opacity: 1; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/angular/column-pinning-sticky/src/styles.scss b/examples/angular/column-pinning-sticky/src/styles.scss deleted file mode 100644 index eb31fc671f..0000000000 --- a/examples/angular/column-pinning-sticky/src/styles.scss +++ /dev/null @@ -1,50 +0,0 @@ -html { - font-family: sans-serif; - font-size: 14px; -} - -.table-container { - border: 1px solid lightgray; - overflow-x: scroll; - width: 100%; - max-width: 960px; - position: relative; -} - -table { - /* box-shadow and borders will not work with position: sticky otherwise */ - border-collapse: separate !important; - border-spacing: 0; -} - -th { - background-color: lightgray; - border-bottom: 1px solid lightgray; - font-weight: bold; - height: 30px; - padding: 2px 4px; - position: relative; - text-align: center; -} - -td { - background-color: white; - padding: 2px 4px; -} - -.resizer { - background: rgba(0, 0, 0, 0.5); - cursor: col-resize; - height: 100%; - position: absolute; - right: 0; - top: 0; - touch-action: none; - user-select: none; - width: 5px; -} - -.resizer.isResizing { - background: blue; - opacity: 1; -} diff --git a/examples/angular/column-pinning-sticky/tsconfig.app.json b/examples/angular/column-pinning-sticky/tsconfig.app.json index 84f1f992d2..a0dcc37c60 100644 --- a/examples/angular/column-pinning-sticky/tsconfig.app.json +++ b/examples/angular/column-pinning-sticky/tsconfig.app.json @@ -1,10 +1,11 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/app", "types": [] }, - "files": ["src/main.ts"], - "include": ["src/**/*.d.ts"] + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] } diff --git a/examples/angular/column-pinning-sticky/tsconfig.json b/examples/angular/column-pinning-sticky/tsconfig.json index b58d3efc71..32789cdc2b 100644 --- a/examples/angular/column-pinning-sticky/tsconfig.json +++ b/examples/angular/column-pinning-sticky/tsconfig.json @@ -1,31 +1,23 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ { "compileOnSave": false, "compilerOptions": { - "baseUrl": "src", - "outDir": "./dist/out-tsc", - "forceConsistentCasingInFileNames": true, "strict": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "skipLibCheck": true, - "esModuleInterop": true, - "sourceMap": true, - "declaration": false, + "isolatedModules": true, "experimentalDecorators": true, - "moduleResolution": "node", "importHelpers": true, "target": "ES2022", - "module": "ES2022", - "useDefineForClassFields": false, - "lib": ["ES2022", "dom"] + "module": "preserve" }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": true, "strictInputAccessModifiers": true, "strictTemplates": true - } + }, + "include": ["**/*.ts", "vite.config.js", "vite.config.ts"] } diff --git a/examples/angular/column-pinning-sticky/tsconfig.spec.json b/examples/angular/column-pinning-sticky/tsconfig.spec.json deleted file mode 100644 index 47e3dd7551..0000000000 --- a/examples/angular/column-pinning-sticky/tsconfig.spec.json +++ /dev/null @@ -1,9 +0,0 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/spec", - "types": ["jasmine"] - }, - "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] -} diff --git a/examples/angular/column-pinning/.devcontainer/devcontainer.json b/examples/angular/column-pinning/.devcontainer/devcontainer.json deleted file mode 100644 index 36f47d8762..0000000000 --- a/examples/angular/column-pinning/.devcontainer/devcontainer.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "Node.js", - "image": "mcr.microsoft.com/devcontainers/javascript-node:18" -} diff --git a/examples/angular/column-pinning/.editorconfig b/examples/angular/column-pinning/.editorconfig deleted file mode 100644 index 59d9a3a3e7..0000000000 --- a/examples/angular/column-pinning/.editorconfig +++ /dev/null @@ -1,16 +0,0 @@ -# Editor configuration, see https://editorconfig.org -root = true - -[*] -charset = utf-8 -indent_style = space -indent_size = 2 -insert_final_newline = true -trim_trailing_whitespace = true - -[*.ts] -quote_type = single - -[*.md] -max_line_length = off -trim_trailing_whitespace = false diff --git a/examples/angular/column-pinning/.gitignore b/examples/angular/column-pinning/.gitignore index 0711527ef9..854acd5fc0 100644 --- a/examples/angular/column-pinning/.gitignore +++ b/examples/angular/column-pinning/.gitignore @@ -1,4 +1,4 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. # Compiled output /dist @@ -26,6 +26,7 @@ yarn-error.log !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json +!.vscode/mcp.json .history/* # Miscellaneous @@ -36,6 +37,7 @@ yarn-error.log /libpeerconnection.log testem.log /typings +__screenshots__/ # System files .DS_Store diff --git a/examples/angular/column-pinning/README.md b/examples/angular/column-pinning/README.md index 5da97a87d1..d6011dbf13 100644 --- a/examples/angular/column-pinning/README.md +++ b/examples/angular/column-pinning/README.md @@ -1,27 +1,59 @@ -# Basic +# Column Pinning -This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.1.2. +This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.1.0. ## Development server -Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. +To start a local development server, run: + +```bash +ng serve +``` + +Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files. ## Code scaffolding -Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. +Angular CLI includes powerful code scaffolding tools. To generate a new component, run: + +```bash +ng generate component component-name +``` + +For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run: + +```bash +ng generate --help +``` -## Build +## Building -Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. +To build the project run: + +```bash +ng build +``` + +This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed. ## Running unit tests -Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). +To execute unit tests with the [Vitest](https://vitest.dev/) test runner, use the following command: + +```bash +ng test +``` ## Running end-to-end tests -Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. +For end-to-end (e2e) testing, run: + +```bash +ng e2e +``` + +Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs. -## Further help +## Additional Resources -To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. +For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. diff --git a/examples/angular/column-pinning/angular.json b/examples/angular/column-pinning/angular.json index b458c5d4d6..006f7a7b9e 100644 --- a/examples/angular/column-pinning/angular.json +++ b/examples/angular/column-pinning/angular.json @@ -1,18 +1,43 @@ { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, + "cli": { + "packageManager": "pnpm", + "analytics": false, + "cache": { + "enabled": false + } + }, "newProjectRoot": "projects", "projects": { - "basic": { - "cli": { - "cache": { - "enabled": false - } - }, + "column-pinning": { "projectType": "application", "schematics": { "@schematics/angular:component": { - "style": "scss" + "inlineTemplate": true, + "inlineStyle": true, + "skipTests": true + }, + "@schematics/angular:class": { + "skipTests": true + }, + "@schematics/angular:directive": { + "skipTests": true + }, + "@schematics/angular:guard": { + "skipTests": true + }, + "@schematics/angular:interceptor": { + "skipTests": true + }, + "@schematics/angular:pipe": { + "skipTests": true + }, + "@schematics/angular:resolver": { + "skipTests": true + }, + "@schematics/angular:service": { + "skipTests": true } }, "root": "", @@ -20,21 +45,32 @@ "prefix": "app", "architect": { "build": { - "builder": "@angular-devkit/build-angular:application", + "builder": "@angular/build:application", "options": { - "outputPath": "dist/column-pinning", - "index": "src/index.html", "browser": "src/main.ts", - "polyfills": ["zone.js"], "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": ["src/favicon.ico", "src/assets"], - "styles": ["src/styles.scss"], - "scripts": [] + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": ["src/styles.css"] }, "configurations": { "production": { - "budgets": [], + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kB", + "maximumError": "8kB" + } + ], "outputHashing": "all" }, "development": { @@ -46,38 +82,18 @@ "defaultConfiguration": "production" }, "serve": { - "builder": "@angular-devkit/build-angular:dev-server", + "builder": "@angular/build:dev-server", "configurations": { "production": { - "buildTarget": "basic:build:production" + "buildTarget": "column-pinning:build:production" }, "development": { - "buildTarget": "basic:build:development" + "buildTarget": "column-pinning:build:development" } }, "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "buildTarget": "basic:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "polyfills": ["zone.js", "zone.js/testing"], - "tsConfig": "tsconfig.spec.json", - "inlineStyleLanguage": "scss", - "assets": ["src/favicon.ico", "src/assets"], - "styles": ["src/styles.scss"], - "scripts": [] - } } } } - }, - "cli": { - "analytics": false } } diff --git a/examples/angular/column-pinning/package.json b/examples/angular/column-pinning/package.json index 3de8173aaa..cbca0655b4 100644 --- a/examples/angular/column-pinning/package.json +++ b/examples/angular/column-pinning/package.json @@ -1,39 +1,31 @@ { - "name": "tanstack-table-example-angular-column-pinning", - "version": "0.0.0", + "name": "tanstack-angular-table-example-column-pinning", "scripts": { "ng": "ng", - "start": "ng serve", + "start": "ng serve --port 5173", + "dev": "ng serve --port 5173", "build": "ng build", - "watch": "ng build --watch --configuration development", - "test": "ng test" + "lint": "eslint ./src", + "watch": "ng build --watch --configuration development" }, "private": true, + "packageManager": "pnpm@11.0.9", "dependencies": { - "@angular/animations": "^17.3.9", - "@angular/common": "^17.3.9", - "@angular/compiler": "^17.3.9", - "@angular/core": "^17.3.9", - "@angular/forms": "^17.3.9", - "@angular/platform-browser": "^17.3.9", - "@angular/platform-browser-dynamic": "^17.3.9", - "@faker-js/faker": "^8.4.1", - "@tanstack/angular-table": "^8.20.5", - "rxjs": "~7.8.1", - "zone.js": "~0.14.4" + "@angular/common": "^21.2.12", + "@angular/compiler": "^21.2.12", + "@angular/core": "^21.2.12", + "@angular/forms": "^21.2.12", + "@angular/platform-browser": "^21.2.12", + "@angular/router": "^21.2.12", + "@faker-js/faker": "^10.4.0", + "@tanstack/angular-table": "^9.0.0-alpha.45", + "rxjs": "~7.8.2", + "tslib": "^2.8.1" }, "devDependencies": { - "@angular-devkit/build-angular": "^17.3.8", - "@angular/cli": "^17.3.8", - "@angular/compiler-cli": "^17.3.9", - "@types/jasmine": "~5.1.4", - "jasmine-core": "~5.1.2", - "karma": "~6.4.3", - "karma-chrome-launcher": "~3.2.0", - "karma-coverage": "~2.2.1", - "karma-jasmine": "~5.1.0", - "karma-jasmine-html-reporter": "~2.1.0", - "tslib": "^2.6.2", - "typescript": "5.4.5" + "@angular/build": "^21.2.10", + "@angular/cli": "^21.2.10", + "@angular/compiler-cli": "^21.2.12", + "typescript": "6.0.3" } } diff --git a/examples/angular/column-visibility/src/favicon.ico b/examples/angular/column-pinning/public/favicon.ico similarity index 100% rename from examples/angular/column-visibility/src/favicon.ico rename to examples/angular/column-pinning/public/favicon.ico diff --git a/examples/angular/column-pinning/src/app/app.component.html b/examples/angular/column-pinning/src/app/app.component.html deleted file mode 100644 index e002af2b70..0000000000 --- a/examples/angular/column-pinning/src/app/app.component.html +++ /dev/null @@ -1,261 +0,0 @@ -
-
-
- -
- - @for (column of table.getAllLeafColumns(); track column.id) { -
- -
- } -
- -
- -
- - -
-
-
- -
- -
- - @if (split()) { - - - @for ( - headerGroup of table.getLeftHeaderGroups(); - track headerGroup.id - ) { - - @for (header of headerGroup.headers; track header.id) { - - } - - } - - - - @for (row of table.getRowModel().rows | slice: 0 : 20; track row.id) { - - @for (cell of row.getLeftVisibleCells(); track cell.id) { - - } - - } - -
-
- @if (!header.isPlaceholder) { - - {{ headerValue }} - - } -
- - -
- - {{ cellValue }} - -
- } - - - - - @if ( - split() ? table.getCenterHeaderGroups() : table.getHeaderGroups(); - as headerGroups - ) { - @for (headerGroup of headerGroups; track headerGroup.id) { - - @for (header of headerGroup.headers; track header.id) { - - } - - } - } - - - @for (row of table.getRowModel().rows | slice: 0 : 20; track row.id) { - @if ( - split() ? row.getCenterVisibleCells() : row.getVisibleCells(); - as cells - ) { - - @for (cell of cells; track cell.id) { - - } - - } - } - -
-
- @if (!header.isPlaceholder) { - - {{ headerValue }} - - } - - -
-
- - {{ cellValue }} - -
- - - @if (split()) { - - - @for ( - headerGroup of table.getRightHeaderGroups(); - track headerGroup.id - ) { - - @for (header of headerGroup.headers; track header.id) { - - } - - } - - - - @for (row of table.getRowModel().rows | slice: 0 : 20; track row.id) { - - @for (cell of row.getRightVisibleCells(); track cell.id) { - - } - - } - -
-
- @if (!header.isPlaceholder) { - - {{ headerValue }} - - } -
- - -
- - {{ cellValue }} - -
- } -
- -
-
{{ stringifiedColumnPinning() }}
-
- - - @if (!header.isPlaceholder && header.column.getCanPin()) { -
- @if (header.column.getIsPinned() !== 'left') { - - } - - @if (header.column.getIsPinned()) { - - } - - @if (header.column.getIsPinned() !== 'right') { - - } -
- } -
diff --git a/examples/angular/column-pinning/src/app/app.component.ts b/examples/angular/column-pinning/src/app/app.component.ts deleted file mode 100644 index 3784a0782c..0000000000 --- a/examples/angular/column-pinning/src/app/app.component.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - computed, - signal, -} from '@angular/core' -import { - ColumnDef, - type ColumnOrderState, - type ColumnPinningState, - createAngularTable, - FlexRenderDirective, - getCoreRowModel, - type VisibilityState, -} from '@tanstack/angular-table' -import { makeData } from './makeData' -import { faker } from '@faker-js/faker' -import { NgTemplateOutlet, SlicePipe } from '@angular/common' - -type Person = { - firstName: string - lastName: string - age: number - visits: number - status: string - progress: number -} - -const defaultColumns: ColumnDef[] = [ - { - header: 'Name', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'firstName', - cell: info => info.getValue(), - footer: props => props.column.id, - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - cell: info => info.getValue(), - header: () => 'Last Name', - footer: props => props.column.id, - }, - ], - }, - { - header: 'Info', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'age', - header: () => 'Age', - footer: props => props.column.id, - }, - { - header: 'More Info', - columns: [ - { - accessorKey: 'visits', - header: () => 'Visits', - footer: props => props.column.id, - }, - { - accessorKey: 'status', - header: 'Status', - footer: props => props.column.id, - }, - { - accessorKey: 'progress', - header: 'Profile Progress', - footer: props => props.column.id, - }, - ], - }, - ], - }, -] - -@Component({ - selector: 'app-root', - standalone: true, - imports: [FlexRenderDirective, SlicePipe, NgTemplateOutlet], - templateUrl: './app.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AppComponent { - readonly data = signal(makeData(5000)) - readonly columnVisibility = signal({}) - readonly columnOrder = signal([]) - readonly columnPinning = signal({}) - readonly split = signal(false) - - table = createAngularTable(() => ({ - data: this.data(), - columns: defaultColumns, - state: { - columnVisibility: this.columnVisibility(), - columnOrder: this.columnOrder(), - columnPinning: this.columnPinning(), - }, - onColumnVisibilityChange: updaterOrValue => { - typeof updaterOrValue === 'function' - ? this.columnVisibility.update(updaterOrValue) - : this.columnVisibility.set(updaterOrValue) - }, - onColumnOrderChange: updaterOrValue => { - typeof updaterOrValue === 'function' - ? this.columnOrder.update(updaterOrValue) - : this.columnOrder.set(updaterOrValue) - }, - onColumnPinningChange: updaterOrValue => { - typeof updaterOrValue === 'function' - ? this.columnPinning.update(updaterOrValue) - : this.columnPinning.set(updaterOrValue) - }, - getCoreRowModel: getCoreRowModel(), - debugTable: true, - debugHeaders: true, - debugColumns: true, - })) - - stringifiedColumnPinning = computed(() => { - return JSON.stringify(this.table.getState().columnPinning) - }) - - randomizeColumns() { - this.table.setColumnOrder( - faker.helpers.shuffle(this.table.getAllLeafColumns().map(d => d.id)) - ) - } - - rerender() { - this.data.set(makeData(5000)) - } -} diff --git a/examples/angular/column-pinning/src/app/app.config.ts b/examples/angular/column-pinning/src/app/app.config.ts index f27099f33c..cbb47d366c 100644 --- a/examples/angular/column-pinning/src/app/app.config.ts +++ b/examples/angular/column-pinning/src/app/app.config.ts @@ -1,5 +1,6 @@ -import { ApplicationConfig } from '@angular/core' +import { provideBrowserGlobalErrorListeners } from '@angular/core' +import type { ApplicationConfig } from '@angular/core' export const appConfig: ApplicationConfig = { - providers: [], + providers: [provideBrowserGlobalErrorListeners()], } diff --git a/examples/angular/column-pinning/src/app/app.html b/examples/angular/column-pinning/src/app/app.html new file mode 100644 index 0000000000..1762a5ac96 --- /dev/null +++ b/examples/angular/column-pinning/src/app/app.html @@ -0,0 +1,225 @@ +
+
+
+ +
+ + @for (column of table.getAllLeafColumns(); track column.id) { +
+ +
+ } +
+ +
+ +
+ + + +
+
+
+ +
+ +
+ + @if (split()) { + + + @for (headerGroup of table.getLeftHeaderGroups(); track headerGroup.id) { + + @for (header of headerGroup.headers; track header.id) { + + } + + } + + + + @for (row of table.getRowModel().rows | slice: 0 : 20; track row.id) { + + @for (cell of row.getLeftVisibleCells(); track cell.id) { + + } + + } + +
+
+ @if (!header.isPlaceholder) { + + {{ headerValue }} + + } +
+ + +
+ + {{ cellValue }} + +
+ } + + + + + @if (split() ? table.getCenterHeaderGroups() : table.getHeaderGroups(); as headerGroups) { + @for (headerGroup of headerGroups; track headerGroup.id) { + + @for (header of headerGroup.headers; track header.id) { + + } + + } + } + + + @for (row of table.getRowModel().rows | slice: 0 : 20; track row.id) { + @if (split() ? row.getCenterVisibleCells() : row.getVisibleCells(); as cells) { + + @for (cell of cells; track cell.id) { + + } + + } + } + +
+
+ @if (!header.isPlaceholder) { + + {{ headerValue }} + + } + + +
+
+ + {{ cellValue }} + +
+ + + @if (split()) { + + + @for (headerGroup of table.getRightHeaderGroups(); track headerGroup.id) { + + @for (header of headerGroup.headers; track header.id) { + + } + + } + + + + @for (row of table.getRowModel().rows | slice: 0 : 20; track row.id) { + + @for (cell of row.getRightVisibleCells(); track cell.id) { + + } + + } + +
+
+ @if (!header.isPlaceholder) { + + {{ headerValue }} + + } +
+ + +
+ + {{ cellValue }} + +
+ } +
+ +
+
{{ stringifiedColumnPinning() }}
+
+ + + @if (!header.isPlaceholder && header.column.getCanPin()) { +
+ @if (header.column.getIsPinned() !== 'left') { + + } + + @if (header.column.getIsPinned()) { + + } + + @if (header.column.getIsPinned() !== 'right') { + + } +
+ } +
diff --git a/examples/angular/column-pinning/src/app/app.ts b/examples/angular/column-pinning/src/app/app.ts new file mode 100644 index 0000000000..34cc9de4b1 --- /dev/null +++ b/examples/angular/column-pinning/src/app/app.ts @@ -0,0 +1,141 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + signal, +} from '@angular/core' +import { + FlexRenderDirective, + columnOrderingFeature, + columnPinningFeature, + columnVisibilityFeature, + injectTable, + tableFeatures, +} from '@tanstack/angular-table' +import { faker } from '@faker-js/faker' +import { NgTemplateOutlet, SlicePipe } from '@angular/common' +import { makeData } from './makeData' +import type { Person } from './makeData' +import type { + ColumnDef, + ColumnOrderState, + ColumnPinningState, + ColumnVisibilityState, +} from '@tanstack/angular-table' + +const _features = tableFeatures({ + columnPinningFeature, + columnOrderingFeature, + columnVisibilityFeature, +}) + +const defaultColumns: Array> = [ + { + header: 'Name', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'firstName', + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => info.getValue(), + header: () => 'Last Name', + footer: (props) => props.column.id, + }, + ], + }, + { + header: 'Info', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'age', + header: () => 'Age', + footer: (props) => props.column.id, + }, + { + header: 'More Info', + columns: [ + { + accessorKey: 'visits', + header: () => 'Visits', + footer: (props) => props.column.id, + }, + { + accessorKey: 'status', + header: 'Status', + footer: (props) => props.column.id, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + footer: (props) => props.column.id, + }, + ], + }, + ], + }, +] + +@Component({ + selector: 'app-root', + imports: [FlexRenderDirective, SlicePipe, NgTemplateOutlet], + templateUrl: './app.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class App { + readonly data = signal>(makeData(20)) + readonly columnVisibility = signal({}) + readonly columnOrder = signal([]) + readonly columnPinning = signal({ + left: [], + right: [], + }) + readonly split = signal(false) + + table = injectTable(() => ({ + _features, + columns: defaultColumns, + data: this.data(), + state: { + columnVisibility: this.columnVisibility(), + columnOrder: this.columnOrder(), + columnPinning: this.columnPinning(), + }, + onColumnVisibilityChange: (updaterOrValue) => { + typeof updaterOrValue === 'function' + ? this.columnVisibility.update(updaterOrValue) + : this.columnVisibility.set(updaterOrValue) + }, + onColumnOrderChange: (updaterOrValue) => { + typeof updaterOrValue === 'function' + ? this.columnOrder.update(updaterOrValue) + : this.columnOrder.set(updaterOrValue) + }, + onColumnPinningChange: (updaterOrValue) => { + typeof updaterOrValue === 'function' + ? this.columnPinning.update(updaterOrValue) + : this.columnPinning.set(updaterOrValue) + }, + debugTable: true, + debugHeaders: true, + debugColumns: true, + })) + + stringifiedColumnPinning = computed(() => { + return JSON.stringify(this.table.store.state.columnPinning) + }) + + randomizeColumns() { + this.table.setColumnOrder( + faker.helpers.shuffle(this.table.getAllLeafColumns().map((d) => d.id)), + ) + } + + refreshData = () => this.data.set(makeData(20)) + stressTest = () => this.data.set(makeData(1_000)) +} diff --git a/examples/angular/column-pinning/src/app/makeData.ts b/examples/angular/column-pinning/src/app/makeData.ts index 331dd1eb19..b9fb014aba 100644 --- a/examples/angular/column-pinning/src/app/makeData.ts +++ b/examples/angular/column-pinning/src/app/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,13 +29,13 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] return range(len).map((d): Person => { return { ...newPerson(), diff --git a/examples/angular/column-pinning/src/assets/.gitkeep b/examples/angular/column-pinning/src/assets/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/examples/angular/column-pinning/src/index.html b/examples/angular/column-pinning/src/index.html index a4bb987648..1b3f817b9e 100644 --- a/examples/angular/column-pinning/src/index.html +++ b/examples/angular/column-pinning/src/index.html @@ -6,7 +6,6 @@ - diff --git a/examples/angular/column-pinning/src/main.ts b/examples/angular/column-pinning/src/main.ts index 0c3b92057c..8192dca694 100644 --- a/examples/angular/column-pinning/src/main.ts +++ b/examples/angular/column-pinning/src/main.ts @@ -1,5 +1,5 @@ import { bootstrapApplication } from '@angular/platform-browser' import { appConfig } from './app/app.config' -import { AppComponent } from './app/app.component' +import { App } from './app/app' -bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err)) +bootstrapApplication(App, appConfig).catch((err) => console.error(err)) diff --git a/examples/angular/column-pinning/src/styles.css b/examples/angular/column-pinning/src/styles.css new file mode 100644 index 0000000000..efd0b26417 --- /dev/null +++ b/examples/angular/column-pinning/src/styles.css @@ -0,0 +1,374 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td { + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td:last-child { + border-right: 0; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/angular/column-pinning/src/styles.scss b/examples/angular/column-pinning/src/styles.scss deleted file mode 100644 index 93034cdd1b..0000000000 --- a/examples/angular/column-pinning/src/styles.scss +++ /dev/null @@ -1,35 +0,0 @@ -html { - font-family: sans-serif; - font-size: 14px; -} - -table { - border: 1px solid lightgray; -} - -tbody { - border-bottom: 1px solid lightgray; -} - -th { - border-bottom: 1px solid lightgray; - border-right: 1px solid lightgray; - padding: 2px 4px; -} - -td { - border-right: 1px solid lightgray; - padding: 2px 4px; -} - -td:last-child { - border-right: 0; -} - -tfoot { - color: gray; -} - -tfoot th { - font-weight: normal; -} diff --git a/examples/angular/column-pinning/tsconfig.app.json b/examples/angular/column-pinning/tsconfig.app.json index 84f1f992d2..a0dcc37c60 100644 --- a/examples/angular/column-pinning/tsconfig.app.json +++ b/examples/angular/column-pinning/tsconfig.app.json @@ -1,10 +1,11 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/app", "types": [] }, - "files": ["src/main.ts"], - "include": ["src/**/*.d.ts"] + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] } diff --git a/examples/angular/column-pinning/tsconfig.json b/examples/angular/column-pinning/tsconfig.json index b58d3efc71..32789cdc2b 100644 --- a/examples/angular/column-pinning/tsconfig.json +++ b/examples/angular/column-pinning/tsconfig.json @@ -1,31 +1,23 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ { "compileOnSave": false, "compilerOptions": { - "baseUrl": "src", - "outDir": "./dist/out-tsc", - "forceConsistentCasingInFileNames": true, "strict": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "skipLibCheck": true, - "esModuleInterop": true, - "sourceMap": true, - "declaration": false, + "isolatedModules": true, "experimentalDecorators": true, - "moduleResolution": "node", "importHelpers": true, "target": "ES2022", - "module": "ES2022", - "useDefineForClassFields": false, - "lib": ["ES2022", "dom"] + "module": "preserve" }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": true, "strictInputAccessModifiers": true, "strictTemplates": true - } + }, + "include": ["**/*.ts", "vite.config.js", "vite.config.ts"] } diff --git a/examples/angular/column-pinning/tsconfig.spec.json b/examples/angular/column-pinning/tsconfig.spec.json deleted file mode 100644 index 47e3dd7551..0000000000 --- a/examples/angular/column-pinning/tsconfig.spec.json +++ /dev/null @@ -1,9 +0,0 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/spec", - "types": ["jasmine"] - }, - "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] -} diff --git a/examples/angular/column-resizing-performant/.gitignore b/examples/angular/column-resizing-performant/.gitignore new file mode 100644 index 0000000000..854acd5fc0 --- /dev/null +++ b/examples/angular/column-resizing-performant/.gitignore @@ -0,0 +1,44 @@ +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out + +# Node +/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/mcp.json +.history/* + +# Miscellaneous +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings +__screenshots__/ + +# System files +.DS_Store +Thumbs.db diff --git a/examples/angular/column-resizing-performant/README.md b/examples/angular/column-resizing-performant/README.md new file mode 100644 index 0000000000..935ca90835 --- /dev/null +++ b/examples/angular/column-resizing-performant/README.md @@ -0,0 +1,59 @@ +# Column Resizing Performant + +This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.1.0. + +## Development server + +To start a local development server, run: + +```bash +ng serve +``` + +Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files. + +## Code scaffolding + +Angular CLI includes powerful code scaffolding tools. To generate a new component, run: + +```bash +ng generate component component-name +``` + +For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run: + +```bash +ng generate --help +``` + +## Building + +To build the project run: + +```bash +ng build +``` + +This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed. + +## Running unit tests + +To execute unit tests with the [Vitest](https://vitest.dev/) test runner, use the following command: + +```bash +ng test +``` + +## Running end-to-end tests + +For end-to-end (e2e) testing, run: + +```bash +ng e2e +``` + +Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs. + +## Additional Resources + +For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. diff --git a/examples/angular/column-resizing-performant/angular.json b/examples/angular/column-resizing-performant/angular.json new file mode 100644 index 0000000000..1afab3f56f --- /dev/null +++ b/examples/angular/column-resizing-performant/angular.json @@ -0,0 +1,99 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "cli": { + "packageManager": "pnpm", + "analytics": false, + "cache": { + "enabled": false + } + }, + "newProjectRoot": "projects", + "projects": { + "column-pinning-sticky": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "inlineTemplate": true, + "inlineStyle": true, + "skipTests": true + }, + "@schematics/angular:class": { + "skipTests": true + }, + "@schematics/angular:directive": { + "skipTests": true + }, + "@schematics/angular:guard": { + "skipTests": true + }, + "@schematics/angular:interceptor": { + "skipTests": true + }, + "@schematics/angular:pipe": { + "skipTests": true + }, + "@schematics/angular:resolver": { + "skipTests": true + }, + "@schematics/angular:service": { + "skipTests": true + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular/build:application", + "options": { + "browser": "src/main.ts", + "tsConfig": "tsconfig.app.json", + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": ["src/styles.css"] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kB", + "maximumError": "8kB" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular/build:dev-server", + "configurations": { + "production": { + "buildTarget": "column-pinning-sticky:build:production" + }, + "development": { + "buildTarget": "column-pinning-sticky:build:development" + } + }, + "defaultConfiguration": "development" + } + } + } + } +} diff --git a/examples/angular/column-resizing-performant/package.json b/examples/angular/column-resizing-performant/package.json new file mode 100644 index 0000000000..7a70e74d4b --- /dev/null +++ b/examples/angular/column-resizing-performant/package.json @@ -0,0 +1,31 @@ +{ + "name": "tanstack-angular-table-example-column-resizing-performant", + "scripts": { + "ng": "ng", + "start": "ng serve --port 5173", + "dev": "ng serve --port 5173", + "build": "ng build", + "lint": "eslint ./src", + "watch": "ng build --watch --configuration development" + }, + "private": true, + "packageManager": "pnpm@11.0.9", + "dependencies": { + "@angular/common": "^21.2.12", + "@angular/compiler": "^21.2.12", + "@angular/core": "^21.2.12", + "@angular/forms": "^21.2.12", + "@angular/platform-browser": "^21.2.12", + "@angular/router": "^21.2.12", + "@faker-js/faker": "^10.4.0", + "@tanstack/angular-table": "^9.0.0-alpha.45", + "rxjs": "~7.8.2", + "tslib": "^2.8.1" + }, + "devDependencies": { + "@angular/build": "^21.2.10", + "@angular/cli": "^21.2.10", + "@angular/compiler-cli": "^21.2.12", + "typescript": "6.0.3" + } +} diff --git a/examples/angular/filters/src/favicon.ico b/examples/angular/column-resizing-performant/public/favicon.ico similarity index 100% rename from examples/angular/filters/src/favicon.ico rename to examples/angular/column-resizing-performant/public/favicon.ico diff --git a/examples/angular/column-resizing-performant/src/app/app.config.ts b/examples/angular/column-resizing-performant/src/app/app.config.ts new file mode 100644 index 0000000000..cbb47d366c --- /dev/null +++ b/examples/angular/column-resizing-performant/src/app/app.config.ts @@ -0,0 +1,6 @@ +import { provideBrowserGlobalErrorListeners } from '@angular/core' +import type { ApplicationConfig } from '@angular/core' + +export const appConfig: ApplicationConfig = { + providers: [provideBrowserGlobalErrorListeners()], +} diff --git a/examples/angular/column-resizing-performant/src/app/app.html b/examples/angular/column-resizing-performant/src/app/app.html new file mode 100644 index 0000000000..fa66af84b2 --- /dev/null +++ b/examples/angular/column-resizing-performant/src/app/app.html @@ -0,0 +1,52 @@ +
+ + +
+
+    {{ columnSizingDebugInfo() }}
+  
+
+ + ({{ data().length.toLocaleString() }} rows) + +
+
+
+ @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { +
+ @for (header of headerGroup.headers; track header.id) { +
+ @if (!header.isPlaceholder) { + +
+
+ } + +
+
+ } +
+ } +
+
+ @for (row of table.getRowModel().rows; track row.id) { +
+ @for (cell of row.getAllCells(); track cell.id) { +
+ +
+
+
+ } +
+ } +
+
+
+
diff --git a/examples/angular/column-resizing-performant/src/app/app.ts b/examples/angular/column-resizing-performant/src/app/app.ts new file mode 100644 index 0000000000..e09963e237 --- /dev/null +++ b/examples/angular/column-resizing-performant/src/app/app.ts @@ -0,0 +1,131 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + signal, + untracked, +} from '@angular/core' +import { + FlexRender, + columnResizingFeature, + columnSizingFeature, + injectTable, + shallow, + tableFeatures, +} from '@tanstack/angular-table' +import { makeData } from './makeData' +import { TableResizableCells } from './resizable-cell/resizable-cell' +import type { Person } from './makeData' +import type { ColumnDef, ColumnResizeMode } from '@tanstack/angular-table' + +const _features = tableFeatures({ + columnSizingFeature, + columnResizingFeature, +}) + +const defaultColumns: Array> = [ + { + header: 'Name', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'firstName', + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => info.getValue(), + header: () => 'Last Name', + footer: (props) => props.column.id, + }, + ], + }, + { + header: 'Info', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'age', + header: () => 'Age', + footer: (props) => props.column.id, + }, + { + accessorKey: 'visits', + header: () => 'Visits', + footer: (props) => props.column.id, + }, + { + accessorKey: 'status', + header: 'Status', + footer: (props) => props.column.id, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + footer: (props) => props.column.id, + }, + ], + }, +] + +@Component({ + selector: 'app-root', + imports: [FlexRender, TableResizableCells], + templateUrl: './app.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class App { + readonly data = signal>(makeData(200)) + + readonly table = injectTable(() => ({ + data: this.data(), + _features, + columns: defaultColumns, + columnResizeMode: 'onChange' as const, + defaultColumn: { + minSize: 60, + maxSize: 800, + }, + debugTable: true, + debugHeaders: true, + debugColumns: true, + })) + + readonly columnSizing = computed(() => this.table.atoms.columnSizing.get(), { + equal: shallow, + }) + + /** + * Instead of calling `column.getSize()` on every render for every header + * and especially every data cell (very expensive), + * we will calculate all column sizes at once at the root table level in a useMemo + * and pass the column sizes down as CSS variables to the element. + */ + readonly columnSizeVars = computed(() => { + void this.columnSizing() + const headers = untracked(() => this.table.getFlatHeaders()) + const colSizes: { [key: string]: number } = {} + let i = headers.length + while (--i >= 0) { + const header = headers[i] + colSizes[`--header-${header.id}-size`] = header.getSize() + colSizes[`--col-${header.column.id}-size`] = header.column.getSize() + } + return colSizes + }) + + readonly columnSizingDebugInfo = computed(() => + JSON.stringify( + { + columnSizing: this.columnSizing(), + }, + null, + 2, + ), + ) + + refreshData = () => this.data.set(makeData(200)) + stressTest = () => this.data.set(makeData(2_000)) +} diff --git a/examples/angular/column-resizing-performant/src/app/makeData.ts b/examples/angular/column-resizing-performant/src/app/makeData.ts new file mode 100644 index 0000000000..b9fb014aba --- /dev/null +++ b/examples/angular/column-resizing-performant/src/app/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/angular/column-resizing-performant/src/app/resizable-cell/resizable-cell.ts b/examples/angular/column-resizing-performant/src/app/resizable-cell/resizable-cell.ts new file mode 100644 index 0000000000..99647b4ebe --- /dev/null +++ b/examples/angular/column-resizing-performant/src/app/resizable-cell/resizable-cell.ts @@ -0,0 +1,40 @@ +import { Directive, computed, input } from '@angular/core' + +@Directive({ + selector: '[tableResizableHeader]', + host: { + '[style.width]': 'width()', + }, + standalone: true, +}) +export class TableResizableHeader { + readonly cellId = input.required({ + alias: 'tableResizableHeader', + }) + + readonly width = computed( + () => `calc(var(--header-${this.cellId()}-size) * 1px)`, + ) +} + +@Directive({ + selector: '[tableResizableCell]', + host: { + '[style.width]': 'width()', + }, + standalone: true, +}) +export class TableResizableCell { + readonly cellId = input.required({ + alias: 'tableResizableCell', + }) + + readonly width = computed( + () => `calc(var(--col-${this.cellId()}-size) * 1px)`, + ) +} + +export const TableResizableCells = [ + TableResizableCell, + TableResizableHeader, +] as const diff --git a/examples/angular/column-resizing-performant/src/index.html b/examples/angular/column-resizing-performant/src/index.html new file mode 100644 index 0000000000..b2a57738e6 --- /dev/null +++ b/examples/angular/column-resizing-performant/src/index.html @@ -0,0 +1,13 @@ + + + + + Performant Column Resizing + + + + + + + + diff --git a/examples/angular/column-resizing-performant/src/main.ts b/examples/angular/column-resizing-performant/src/main.ts new file mode 100644 index 0000000000..8192dca694 --- /dev/null +++ b/examples/angular/column-resizing-performant/src/main.ts @@ -0,0 +1,5 @@ +import { bootstrapApplication } from '@angular/platform-browser' +import { appConfig } from './app/app.config' +import { App } from './app/app' + +bootstrapApplication(App, appConfig).catch((err) => console.error(err)) diff --git a/examples/angular/column-resizing-performant/src/styles.css b/examples/angular/column-resizing-performant/src/styles.css new file mode 100644 index 0000000000..cf03d434ac --- /dev/null +++ b/examples/angular/column-resizing-performant/src/styles.css @@ -0,0 +1,413 @@ +* { + box-sizing: border-box; +} + +html { + font-family: sans-serif; + font-size: 14px; +} + +table, +.divTable { + display: block; + border: 1px solid lightgray; + width: fit-content; + border-collapse: collapse; + border-spacing: 0; +} + +.tr { + display: flex; +} + +tr, +.tr { + width: fit-content; + height: 30px; +} + +th, +.th, +td, +.td { + box-shadow: inset 0 0 0 1px lightgray; + padding: 0.25rem; +} + +th, +.th { + padding: 2px 4px; + position: relative; + font-weight: bold; + text-align: center; + height: 30px; +} + +td, +.td { + height: 30px; +} + +.resizer { + position: absolute; + top: 0; + height: 100%; + right: 0; + width: 5px; + background: rgba(0, 0, 0, 0.5); + cursor: col-resize; + user-select: none; + touch-action: none; +} + +.resizer.isResizing { + background: blue; + opacity: 1; +} + +@media (hover: hover) { + .resizer { + opacity: 0; + } + + *:hover > .resizer { + opacity: 1; + } +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/angular/column-resizing-performant/tsconfig.app.json b/examples/angular/column-resizing-performant/tsconfig.app.json new file mode 100644 index 0000000000..a0dcc37c60 --- /dev/null +++ b/examples/angular/column-resizing-performant/tsconfig.app.json @@ -0,0 +1,11 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] +} diff --git a/examples/angular/column-resizing-performant/tsconfig.json b/examples/angular/column-resizing-performant/tsconfig.json new file mode 100644 index 0000000000..32789cdc2b --- /dev/null +++ b/examples/angular/column-resizing-performant/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "isolatedModules": true, + "experimentalDecorators": true, + "importHelpers": true, + "target": "ES2022", + "module": "preserve" + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + }, + "include": ["**/*.ts", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/angular/column-visibility/.devcontainer/devcontainer.json b/examples/angular/column-visibility/.devcontainer/devcontainer.json deleted file mode 100644 index 36f47d8762..0000000000 --- a/examples/angular/column-visibility/.devcontainer/devcontainer.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "Node.js", - "image": "mcr.microsoft.com/devcontainers/javascript-node:18" -} diff --git a/examples/angular/column-visibility/.editorconfig b/examples/angular/column-visibility/.editorconfig deleted file mode 100644 index 59d9a3a3e7..0000000000 --- a/examples/angular/column-visibility/.editorconfig +++ /dev/null @@ -1,16 +0,0 @@ -# Editor configuration, see https://editorconfig.org -root = true - -[*] -charset = utf-8 -indent_style = space -indent_size = 2 -insert_final_newline = true -trim_trailing_whitespace = true - -[*.ts] -quote_type = single - -[*.md] -max_line_length = off -trim_trailing_whitespace = false diff --git a/examples/angular/column-visibility/.gitignore b/examples/angular/column-visibility/.gitignore index 0711527ef9..854acd5fc0 100644 --- a/examples/angular/column-visibility/.gitignore +++ b/examples/angular/column-visibility/.gitignore @@ -1,4 +1,4 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. # Compiled output /dist @@ -26,6 +26,7 @@ yarn-error.log !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json +!.vscode/mcp.json .history/* # Miscellaneous @@ -36,6 +37,7 @@ yarn-error.log /libpeerconnection.log testem.log /typings +__screenshots__/ # System files .DS_Store diff --git a/examples/angular/column-visibility/README.md b/examples/angular/column-visibility/README.md index 5da97a87d1..ce3f144d0b 100644 --- a/examples/angular/column-visibility/README.md +++ b/examples/angular/column-visibility/README.md @@ -1,27 +1,59 @@ -# Basic +# Column Visibility -This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.1.2. +This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.1.0. ## Development server -Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. +To start a local development server, run: + +```bash +ng serve +``` + +Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files. ## Code scaffolding -Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. +Angular CLI includes powerful code scaffolding tools. To generate a new component, run: + +```bash +ng generate component component-name +``` + +For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run: + +```bash +ng generate --help +``` -## Build +## Building -Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. +To build the project run: + +```bash +ng build +``` + +This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed. ## Running unit tests -Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). +To execute unit tests with the [Vitest](https://vitest.dev/) test runner, use the following command: + +```bash +ng test +``` ## Running end-to-end tests -Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. +For end-to-end (e2e) testing, run: + +```bash +ng e2e +``` + +Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs. -## Further help +## Additional Resources -To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. +For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. diff --git a/examples/angular/column-visibility/angular.json b/examples/angular/column-visibility/angular.json index 3c9f089af8..fb1aa6a527 100644 --- a/examples/angular/column-visibility/angular.json +++ b/examples/angular/column-visibility/angular.json @@ -1,18 +1,43 @@ { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, + "cli": { + "packageManager": "pnpm", + "analytics": false, + "cache": { + "enabled": false + } + }, "newProjectRoot": "projects", "projects": { - "basic": { - "cli": { - "cache": { - "enabled": false - } - }, + "column-visibility": { "projectType": "application", "schematics": { "@schematics/angular:component": { - "style": "scss" + "inlineTemplate": true, + "inlineStyle": true, + "skipTests": true + }, + "@schematics/angular:class": { + "skipTests": true + }, + "@schematics/angular:directive": { + "skipTests": true + }, + "@schematics/angular:guard": { + "skipTests": true + }, + "@schematics/angular:interceptor": { + "skipTests": true + }, + "@schematics/angular:pipe": { + "skipTests": true + }, + "@schematics/angular:resolver": { + "skipTests": true + }, + "@schematics/angular:service": { + "skipTests": true } }, "root": "", @@ -20,21 +45,32 @@ "prefix": "app", "architect": { "build": { - "builder": "@angular-devkit/build-angular:application", + "builder": "@angular/build:application", "options": { - "outputPath": "dist/column-visibility", - "index": "src/index.html", "browser": "src/main.ts", - "polyfills": ["zone.js"], "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": ["src/favicon.ico", "src/assets"], - "styles": ["src/styles.scss"], - "scripts": [] + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": ["src/styles.css"] }, "configurations": { "production": { - "budgets": [], + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kB", + "maximumError": "8kB" + } + ], "outputHashing": "all" }, "development": { @@ -46,38 +82,18 @@ "defaultConfiguration": "production" }, "serve": { - "builder": "@angular-devkit/build-angular:dev-server", + "builder": "@angular/build:dev-server", "configurations": { "production": { - "buildTarget": "basic:build:production" + "buildTarget": "column-visibility:build:production" }, "development": { - "buildTarget": "basic:build:development" + "buildTarget": "column-visibility:build:development" } }, "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "buildTarget": "basic:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "polyfills": ["zone.js", "zone.js/testing"], - "tsConfig": "tsconfig.spec.json", - "inlineStyleLanguage": "scss", - "assets": ["src/favicon.ico", "src/assets"], - "styles": ["src/styles.scss"], - "scripts": [] - } } } } - }, - "cli": { - "analytics": false } } diff --git a/examples/angular/column-visibility/package.json b/examples/angular/column-visibility/package.json index 1c0a1d6e24..4ca60070ea 100644 --- a/examples/angular/column-visibility/package.json +++ b/examples/angular/column-visibility/package.json @@ -1,38 +1,31 @@ { - "name": "tanstack-table-example-angular-column-visibility", - "version": "0.0.0", + "name": "tanstack-angular-table-example-column-visibility", "scripts": { "ng": "ng", - "start": "ng serve", + "start": "ng serve --port 5173", + "dev": "ng serve --port 5173", "build": "ng build", - "watch": "ng build --watch --configuration development", - "test": "ng test" + "lint": "eslint ./src", + "watch": "ng build --watch --configuration development" }, "private": true, + "packageManager": "pnpm@11.0.9", "dependencies": { - "@angular/animations": "^17.3.9", - "@angular/common": "^17.3.9", - "@angular/compiler": "^17.3.9", - "@angular/core": "^17.3.9", - "@angular/forms": "^17.3.9", - "@angular/platform-browser": "^17.3.9", - "@angular/platform-browser-dynamic": "^17.3.9", - "@tanstack/angular-table": "^8.20.5", - "rxjs": "~7.8.1", - "zone.js": "~0.14.4" + "@angular/common": "^21.2.12", + "@angular/compiler": "^21.2.12", + "@angular/core": "^21.2.12", + "@angular/forms": "^21.2.12", + "@angular/platform-browser": "^21.2.12", + "@angular/router": "^21.2.12", + "@faker-js/faker": "^10.4.0", + "@tanstack/angular-table": "^9.0.0-alpha.45", + "rxjs": "~7.8.2", + "tslib": "^2.8.1" }, "devDependencies": { - "@angular-devkit/build-angular": "^17.3.8", - "@angular/cli": "^17.3.8", - "@angular/compiler-cli": "^17.3.9", - "@types/jasmine": "~5.1.4", - "jasmine-core": "~5.1.2", - "karma": "~6.4.3", - "karma-chrome-launcher": "~3.2.0", - "karma-coverage": "~2.2.1", - "karma-jasmine": "~5.1.0", - "karma-jasmine-html-reporter": "~2.1.0", - "tslib": "^2.6.2", - "typescript": "5.4.5" + "@angular/build": "^21.2.10", + "@angular/cli": "^21.2.10", + "@angular/compiler-cli": "^21.2.12", + "typescript": "6.0.3" } } diff --git a/examples/angular/grouping/src/favicon.ico b/examples/angular/column-visibility/public/favicon.ico similarity index 100% rename from examples/angular/grouping/src/favicon.ico rename to examples/angular/column-visibility/public/favicon.ico diff --git a/examples/angular/column-visibility/src/app/app.component.html b/examples/angular/column-visibility/src/app/app.component.html deleted file mode 100644 index 18b09bac31..0000000000 --- a/examples/angular/column-visibility/src/app/app.component.html +++ /dev/null @@ -1,99 +0,0 @@ -
-
-
- -
- - @for (column of table.getAllLeafColumns(); track column.id) { -
- -
- } -
- -
- -
- - @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { - - @for (header of headerGroup.headers; track header.id) { - - } - - } - - - @for (row of table.getRowModel().rows; track row.id) { - - @for (cell of row.getVisibleCells(); track cell.id) { - - } - - } - - - @for (footerGroup of table.getFooterGroups(); track footerGroup.id) { - - @for (header of footerGroup.headers; track header.id) { - - } - - } - -
- @if (!header.isPlaceholder) { - - {{ header }} - - } -
- - {{ cell }} - -
- @if (!header.isPlaceholder) { - - {{ header }} - - } -
- -
- - -
-
{{ stringifiedColumnVisibility() }}
- diff --git a/examples/angular/column-visibility/src/app/app.component.ts b/examples/angular/column-visibility/src/app/app.component.ts deleted file mode 100644 index c38a366b3c..0000000000 --- a/examples/angular/column-visibility/src/app/app.component.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - computed, - type OnInit, - signal, -} from '@angular/core' -import { - ColumnDef, - createAngularTable, - FlexRenderDirective, - getCoreRowModel, - type VisibilityState, -} from '@tanstack/angular-table' - -type Person = { - firstName: string - lastName: string - age: number - visits: number - status: string - progress: number -} - -const defaultData: Person[] = [ - { - firstName: 'tanner', - lastName: 'linsley', - age: 24, - visits: 100, - status: 'In Relationship', - progress: 50, - }, - { - firstName: 'tandy', - lastName: 'miller', - age: 40, - visits: 40, - status: 'Single', - progress: 80, - }, - { - firstName: 'joe', - lastName: 'dirte', - age: 45, - visits: 20, - status: 'Complicated', - progress: 10, - }, -] - -const defaultColumns: ColumnDef[] = [ - { - header: 'Name', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'firstName', - cell: info => info.getValue(), - footer: props => props.column.id, - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - cell: info => info.getValue(), - header: () => 'Last Name', - footer: props => props.column.id, - }, - ], - }, - { - header: 'Info', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'age', - header: () => 'Age', - footer: props => props.column.id, - }, - { - header: 'More Info', - columns: [ - { - accessorKey: 'visits', - header: () => 'Visits', - footer: props => props.column.id, - }, - { - accessorKey: 'status', - header: 'Status', - footer: props => props.column.id, - }, - { - accessorKey: 'progress', - header: 'Profile Progress', - footer: props => props.column.id, - }, - ], - }, - ], - }, -] - -@Component({ - selector: 'app-root', - standalone: true, - imports: [FlexRenderDirective], - templateUrl: './app.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AppComponent implements OnInit { - data = signal([]) - readonly columnVisibility = signal({}) - - table = createAngularTable(() => ({ - data: this.data(), - columns: defaultColumns, - state: { - columnVisibility: this.columnVisibility(), - }, - getCoreRowModel: getCoreRowModel(), - onColumnVisibilityChange: updaterOrValue => { - const visibilityState = - typeof updaterOrValue === 'function' - ? updaterOrValue(this.columnVisibility()) - : updaterOrValue - this.columnVisibility.set(visibilityState) - }, - debugTable: true, - debugHeaders: true, - debugColumns: true, - })) - - stringifiedColumnVisibility = computed(() => { - return JSON.stringify(this.table.getState().columnVisibility) - }) - - ngOnInit() { - this.data.set(defaultData) - } - - rerender() { - this.data.set(defaultData) - } -} diff --git a/examples/angular/column-visibility/src/app/app.config.ts b/examples/angular/column-visibility/src/app/app.config.ts index f27099f33c..cbb47d366c 100644 --- a/examples/angular/column-visibility/src/app/app.config.ts +++ b/examples/angular/column-visibility/src/app/app.config.ts @@ -1,5 +1,6 @@ -import { ApplicationConfig } from '@angular/core' +import { provideBrowserGlobalErrorListeners } from '@angular/core' +import type { ApplicationConfig } from '@angular/core' export const appConfig: ApplicationConfig = { - providers: [], + providers: [provideBrowserGlobalErrorListeners()], } diff --git a/examples/angular/column-visibility/src/app/app.html b/examples/angular/column-visibility/src/app/app.html new file mode 100644 index 0000000000..4a883ed014 --- /dev/null +++ b/examples/angular/column-visibility/src/app/app.html @@ -0,0 +1,81 @@ +
+ + +
+
+
+ +
+ + @for (column of table.getAllLeafColumns(); track column.id) { +
+ +
+ } +
+ +
+ + + + @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { + + @for (header of headerGroup.headers; track header.id) { + + } + + } + + + @for (row of table.getRowModel().rows; track row.id) { + + @for (cell of row.getVisibleCells(); track cell.id) { + + } + + } + + + @for (footerGroup of table.getFooterGroups(); track footerGroup.id) { + + @for (header of footerGroup.headers; track header.id) { + + } + + } + +
+ @if (!header.isPlaceholder) { + + {{ header }} + + } +
+ + {{ cell }} + +
+ @if (!header.isPlaceholder) { + + {{ header }} + + } +
+ +
+
{{ stringifiedColumnVisibility() }}
+
diff --git a/examples/angular/column-visibility/src/app/app.ts b/examples/angular/column-visibility/src/app/app.ts new file mode 100644 index 0000000000..5ae88eff7e --- /dev/null +++ b/examples/angular/column-visibility/src/app/app.ts @@ -0,0 +1,108 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + signal, +} from '@angular/core' +import { + FlexRender, + columnVisibilityFeature, + injectTable, + isFunction, + tableFeatures, +} from '@tanstack/angular-table' +import { makeData } from './makeData' +import type { Person } from './makeData' +import type { ColumnDef, ColumnVisibilityState } from '@tanstack/angular-table' + +const _features = tableFeatures({ + columnVisibilityFeature, +}) + +const defaultColumns: Array> = [ + { + header: 'Name', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'firstName', + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => info.getValue(), + header: () => 'Last Name', + footer: (props) => props.column.id, + }, + ], + }, + { + header: 'Info', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'age', + header: () => 'Age', + footer: (props) => props.column.id, + }, + { + header: 'More Info', + columns: [ + { + accessorKey: 'visits', + header: () => 'Visits', + footer: (props) => props.column.id, + }, + { + accessorKey: 'status', + header: 'Status', + footer: (props) => props.column.id, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + footer: (props) => props.column.id, + }, + ], + }, + ], + }, +] + +@Component({ + selector: 'app-root', + imports: [FlexRender], + templateUrl: './app.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class App { + readonly data = signal>(makeData(20)) + readonly columnVisibility = signal({}) + + readonly table = injectTable(() => ({ + _features, + columns: defaultColumns, + data: this.data(), + state: { + columnVisibility: this.columnVisibility(), + }, + onColumnVisibilityChange: (updaterOrValue) => { + const visibilityState = isFunction(updaterOrValue) + ? updaterOrValue(this.columnVisibility()) + : updaterOrValue + this.columnVisibility.set(visibilityState) + }, + debugTable: true, + debugHeaders: true, + debugColumns: true, + })) + + readonly stringifiedColumnVisibility = computed(() => { + return JSON.stringify(this.table.store.state.columnVisibility) + }) + + refreshData = () => this.data.set(makeData(20)) + stressTest = () => this.data.set(makeData(1_000)) +} diff --git a/examples/angular/column-visibility/src/app/makeData.ts b/examples/angular/column-visibility/src/app/makeData.ts new file mode 100644 index 0000000000..b9fb014aba --- /dev/null +++ b/examples/angular/column-visibility/src/app/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/angular/column-visibility/src/assets/.gitkeep b/examples/angular/column-visibility/src/assets/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/examples/angular/column-visibility/src/index.html b/examples/angular/column-visibility/src/index.html index a4bb987648..c6aeab87d7 100644 --- a/examples/angular/column-visibility/src/index.html +++ b/examples/angular/column-visibility/src/index.html @@ -2,11 +2,10 @@ - Basic + Column Visibility - diff --git a/examples/angular/column-visibility/src/main.ts b/examples/angular/column-visibility/src/main.ts index 0c3b92057c..8192dca694 100644 --- a/examples/angular/column-visibility/src/main.ts +++ b/examples/angular/column-visibility/src/main.ts @@ -1,5 +1,5 @@ import { bootstrapApplication } from '@angular/platform-browser' import { appConfig } from './app/app.config' -import { AppComponent } from './app/app.component' +import { App } from './app/app' -bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err)) +bootstrapApplication(App, appConfig).catch((err) => console.error(err)) diff --git a/examples/angular/column-visibility/src/styles.css b/examples/angular/column-visibility/src/styles.css new file mode 100644 index 0000000000..3546a66331 --- /dev/null +++ b/examples/angular/column-visibility/src/styles.css @@ -0,0 +1,371 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +.pagination-actions { + margin: 10px; + display: flex; + gap: 10px; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/angular/column-visibility/src/styles.scss b/examples/angular/column-visibility/src/styles.scss deleted file mode 100644 index cda3113f7d..0000000000 --- a/examples/angular/column-visibility/src/styles.scss +++ /dev/null @@ -1,32 +0,0 @@ -html { - font-family: sans-serif; - font-size: 14px; -} - -table { - border: 1px solid lightgray; -} - -tbody { - border-bottom: 1px solid lightgray; -} - -th { - border-bottom: 1px solid lightgray; - border-right: 1px solid lightgray; - padding: 2px 4px; -} - -tfoot { - color: gray; -} - -tfoot th { - font-weight: normal; -} - -.pagination-actions { - margin: 10px; - display: flex; - gap: 10px; -} diff --git a/examples/angular/column-visibility/tsconfig.app.json b/examples/angular/column-visibility/tsconfig.app.json index 84f1f992d2..a0dcc37c60 100644 --- a/examples/angular/column-visibility/tsconfig.app.json +++ b/examples/angular/column-visibility/tsconfig.app.json @@ -1,10 +1,11 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/app", "types": [] }, - "files": ["src/main.ts"], - "include": ["src/**/*.d.ts"] + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] } diff --git a/examples/angular/column-visibility/tsconfig.json b/examples/angular/column-visibility/tsconfig.json index b58d3efc71..32789cdc2b 100644 --- a/examples/angular/column-visibility/tsconfig.json +++ b/examples/angular/column-visibility/tsconfig.json @@ -1,31 +1,23 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ { "compileOnSave": false, "compilerOptions": { - "baseUrl": "src", - "outDir": "./dist/out-tsc", - "forceConsistentCasingInFileNames": true, "strict": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "skipLibCheck": true, - "esModuleInterop": true, - "sourceMap": true, - "declaration": false, + "isolatedModules": true, "experimentalDecorators": true, - "moduleResolution": "node", "importHelpers": true, "target": "ES2022", - "module": "ES2022", - "useDefineForClassFields": false, - "lib": ["ES2022", "dom"] + "module": "preserve" }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": true, "strictInputAccessModifiers": true, "strictTemplates": true - } + }, + "include": ["**/*.ts", "vite.config.js", "vite.config.ts"] } diff --git a/examples/angular/column-visibility/tsconfig.spec.json b/examples/angular/column-visibility/tsconfig.spec.json deleted file mode 100644 index 47e3dd7551..0000000000 --- a/examples/angular/column-visibility/tsconfig.spec.json +++ /dev/null @@ -1,9 +0,0 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/spec", - "types": ["jasmine"] - }, - "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] -} diff --git a/examples/angular/composable-tables/.gitignore b/examples/angular/composable-tables/.gitignore new file mode 100644 index 0000000000..854acd5fc0 --- /dev/null +++ b/examples/angular/composable-tables/.gitignore @@ -0,0 +1,44 @@ +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out + +# Node +/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/mcp.json +.history/* + +# Miscellaneous +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings +__screenshots__/ + +# System files +.DS_Store +Thumbs.db diff --git a/examples/angular/composable-tables/README.md b/examples/angular/composable-tables/README.md new file mode 100644 index 0000000000..5f48d0ac42 --- /dev/null +++ b/examples/angular/composable-tables/README.md @@ -0,0 +1,59 @@ +# Composable Tables + +This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.1.0. + +## Development server + +To start a local development server, run: + +```bash +ng serve +``` + +Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files. + +## Code scaffolding + +Angular CLI includes powerful code scaffolding tools. To generate a new component, run: + +```bash +ng generate component component-name +``` + +For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run: + +```bash +ng generate --help +``` + +## Building + +To build the project run: + +```bash +ng build +``` + +This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed. + +## Running unit tests + +To execute unit tests with the [Vitest](https://vitest.dev/) test runner, use the following command: + +```bash +ng test +``` + +## Running end-to-end tests + +For end-to-end (e2e) testing, run: + +```bash +ng e2e +``` + +Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs. + +## Additional Resources + +For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. diff --git a/examples/angular/composable-tables/angular.json b/examples/angular/composable-tables/angular.json new file mode 100644 index 0000000000..0ac9fcc7cc --- /dev/null +++ b/examples/angular/composable-tables/angular.json @@ -0,0 +1,99 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "cli": { + "packageManager": "pnpm", + "analytics": false, + "cache": { + "enabled": false + } + }, + "newProjectRoot": "projects", + "projects": { + "composable-tables": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "inlineTemplate": true, + "inlineStyle": true, + "skipTests": true + }, + "@schematics/angular:class": { + "skipTests": true + }, + "@schematics/angular:directive": { + "skipTests": true + }, + "@schematics/angular:guard": { + "skipTests": true + }, + "@schematics/angular:interceptor": { + "skipTests": true + }, + "@schematics/angular:pipe": { + "skipTests": true + }, + "@schematics/angular:resolver": { + "skipTests": true + }, + "@schematics/angular:service": { + "skipTests": true + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular/build:application", + "options": { + "browser": "src/main.ts", + "tsConfig": "tsconfig.app.json", + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": ["src/styles.css"] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kB", + "maximumError": "8kB" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular/build:dev-server", + "configurations": { + "production": { + "buildTarget": "composable-tables:build:production" + }, + "development": { + "buildTarget": "composable-tables:build:development" + } + }, + "defaultConfiguration": "development" + } + } + } + } +} diff --git a/examples/angular/composable-tables/package.json b/examples/angular/composable-tables/package.json new file mode 100644 index 0000000000..6efaf956fd --- /dev/null +++ b/examples/angular/composable-tables/package.json @@ -0,0 +1,31 @@ +{ + "name": "tanstack-angular-table-example-composable-tables", + "scripts": { + "ng": "ng", + "start": "ng serve --port 5173", + "dev": "ng serve --port 5173", + "build": "ng build", + "lint": "eslint ./src", + "watch": "ng build --watch --configuration development" + }, + "private": true, + "packageManager": "pnpm@11.0.9", + "dependencies": { + "@angular/common": "^21.2.12", + "@angular/compiler": "^21.2.12", + "@angular/core": "^21.2.12", + "@angular/forms": "^21.2.12", + "@angular/platform-browser": "^21.2.12", + "@angular/router": "^21.2.12", + "@faker-js/faker": "^10.4.0", + "@tanstack/angular-table": "^9.0.0-alpha.45", + "rxjs": "~7.8.2", + "tslib": "^2.8.1" + }, + "devDependencies": { + "@angular/build": "^21.2.10", + "@angular/cli": "^21.2.10", + "@angular/compiler-cli": "^21.2.12", + "typescript": "6.0.3" + } +} diff --git a/examples/angular/composable-tables/src/app/app.config.ts b/examples/angular/composable-tables/src/app/app.config.ts new file mode 100644 index 0000000000..cbb47d366c --- /dev/null +++ b/examples/angular/composable-tables/src/app/app.config.ts @@ -0,0 +1,6 @@ +import { provideBrowserGlobalErrorListeners } from '@angular/core' +import type { ApplicationConfig } from '@angular/core' + +export const appConfig: ApplicationConfig = { + providers: [provideBrowserGlobalErrorListeners()], +} diff --git a/examples/angular/composable-tables/src/app/app.ts b/examples/angular/composable-tables/src/app/app.ts new file mode 100644 index 0000000000..02e8765d9c --- /dev/null +++ b/examples/angular/composable-tables/src/app/app.ts @@ -0,0 +1,27 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core' +import { UsersTable } from './components/users-table/users-table' +import { ProductsTable } from './components/products-table/products-table' + +@Component({ + selector: 'app-root', + imports: [UsersTable, ProductsTable], + host: { + class: 'app', + }, + template: ` +

Composable Tables Example

+

+ Both tables below use the same injectAppTable function and + shareable components, but with different data types and column + configurations. +

+ + + +
+ + + `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class App {} diff --git a/examples/angular/composable-tables/src/app/components/cell-components.ts b/examples/angular/composable-tables/src/app/components/cell-components.ts new file mode 100644 index 0000000000..3cca4ec1d2 --- /dev/null +++ b/examples/angular/composable-tables/src/app/components/cell-components.ts @@ -0,0 +1,117 @@ +import { Component, computed } from '@angular/core' +import { injectFlexRenderContext } from '@tanstack/angular-table' +import { CurrencyPipe } from '@angular/common' +import { injectTableCellContext } from '../table' +import type { CellContext, TableFeatures } from '@tanstack/angular-table' +import type { Person } from '../makeData' + +@Component({ + // Using dynamic components with Angular, we can just put a simple selector + // that will be rendered, but we need to specify an unique host property + // that will identify this component + selector: 'span', + host: { + 'tanstack-table-text-cell': '', + }, + template: ` {{ cell.getValue() }} `, +}) +export class TextCell { + readonly cell = + injectFlexRenderContext>() +} + +@Component({ + selector: 'span', + host: { + 'tanstack-table-number-cell': '', + }, + template: ` {{ cell.getValue().toLocaleString() }} `, +}) +export class NumberCell { + readonly cell = + injectFlexRenderContext>() +} + +@Component({ + selector: 'span', + host: { + 'tanstack-table-status-cell': '', + '[class.status-badge]': 'true', + '[class]': 'cell().getValue()', + }, + template: ` {{ cell().getValue() }} `, +}) +export class StatusCell { + readonly cell = injectTableCellContext< + 'relationship' | 'complicated' | 'single' + >() +} + +@Component({ + selector: 'table-progress-cell', + template: ` +
+
+ `, +}) +export class ProgressCell { + readonly cell = injectTableCellContext() + + readonly progress = computed(() => this.cell().getValue()) +} + +@Component({ + selector: 'table-row-actions', + template: ` +
+ + + +
+ `, +}) +export class RowActionsCell { + readonly cell = injectTableCellContext() + + view() { + alert( + `View: ${this.cell().row.original.firstName} ${this.cell().row.original.lastName}`, + ) + } + + edit() { + alert( + `Edit: ${this.cell().row.original.firstName} ${this.cell().row.original.lastName}`, + ) + } + + delete() { + alert( + `Delete: ${this.cell().row.original.firstName} ${this.cell().row.original.lastName}`, + ) + } +} + +@Component({ + selector: 'table-price-cell', + template: ` {{ cell().getValue() | currency }} `, + imports: [CurrencyPipe], +}) +export class PriceCell { + readonly cell = injectTableCellContext() +} + +@Component({ + selector: 'span', + host: { + 'tanstack-table-category-cell': '', + '[class.category-badge]': 'true', + '[class]': 'cell().getValue()', + }, + template: ` {{ cell().getValue() }} `, +}) +export class CategoryCell { + readonly cell = injectTableCellContext< + 'electronics' | 'clothing' | 'food' | 'books' + >() +} diff --git a/examples/angular/composable-tables/src/app/components/header-components.ts b/examples/angular/composable-tables/src/app/components/header-components.ts new file mode 100644 index 0000000000..7afeaacb8e --- /dev/null +++ b/examples/angular/composable-tables/src/app/components/header-components.ts @@ -0,0 +1,84 @@ +// export function SortIndicator() { +// const header = useHeaderContext() +// const sorted = header.column.getIsSorted() +// +// if (!sorted) return null +// +// return ( +// {sorted === 'asc' ? '🔼' : '🔽'} +// ) +// } + +import { Component, computed } from '@angular/core' +import { flexRenderComponent } from '@tanstack/angular-table' +import { FormsModule } from '@angular/forms' +import { injectTableHeaderContext } from '../table' +import type { FlexRenderComponent } from '@tanstack/angular-table' + +export function SortIndicator(): string | null { + const header = injectTableHeaderContext() + const sorted = header().column.getIsSorted() + if (!sorted) { + return null + } + return `${sorted === 'asc' ? '🔼' : '🔽'}` +} + +export function ColumnFilter(): FlexRenderComponent | null { + const header = injectTableHeaderContext() + if (!header().column.getCanFilter()) return null + return flexRenderComponent(_ColumnFilter) +} + +@Component({ + template: ` +
+ +
+ `, + imports: [FormsModule], +}) +export class _ColumnFilter { + readonly header = injectTableHeaderContext() + readonly filterValue = computed(() => this.header().column.getFilterValue()) + readonly placeholder = computed(() => `Filter ${this.header().column.id}...`) +} + +@Component({ + selector: 'span', + host: { + 'tanstack-footer-column-id': '', + class: 'footer-column-id', + }, + template: `{{ header().column.id }}`, +}) +export class FooterColumnId { + readonly header = injectTableHeaderContext() +} + +@Component({ + selector: 'span', + host: { + 'tanstack-footer-sum': '', + class: 'footer-sum', + }, + template: `{{ sum() > 0 ? sum().toLocaleString() : '—' }}`, +}) +export class FooterSum { + readonly header = injectTableHeaderContext() + + readonly table = computed(() => this.header().getContext().table) + readonly rows = computed(() => this.table().getFilteredRowModel().rows) + + readonly sum = computed(() => + this.rows().reduce((acc, row) => { + const value = row.getValue(this.header().column.id) + return acc + (typeof value === 'number' ? value : 0) + }, 0), + ) +} diff --git a/examples/angular/composable-tables/src/app/components/products-table/products-table.html b/examples/angular/composable-tables/src/app/components/products-table/products-table.html new file mode 100644 index 0000000000..ad0643dbea --- /dev/null +++ b/examples/angular/composable-tables/src/app/components/products-table/products-table.html @@ -0,0 +1,72 @@ +
+ + + + + @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { + + @for (_header of headerGroup.headers; track _header.id) { + @let header = table.appHeader(_header); + @if (!header.isPlaceholder) { + + } + } + + } + + + @for (row of table.getRowModel().rows; track row.id) { + + @for (cell of row.getAllCells(); track cell.id) { + + } + + } + + + + @for (footerGroup of table.getFooterGroups(); track footerGroup.id) { + + @for (footer of footerGroup.headers; track footer.id) { + + } + + } + +
+ + {{ header }} + + + +
+
+ + +
+
+
+ + {{ cell }} + +
+ @if (!footer.isPlaceholder) { + + {{ footer }} + + } +
+ + + + +
diff --git a/examples/angular/composable-tables/src/app/components/products-table/products-table.ts b/examples/angular/composable-tables/src/app/components/products-table/products-table.ts new file mode 100644 index 0000000000..b1808695a8 --- /dev/null +++ b/examples/angular/composable-tables/src/app/components/products-table/products-table.ts @@ -0,0 +1,71 @@ +import { ChangeDetectionStrategy, Component, signal } from '@angular/core' +import { NgComponentOutlet } from '@angular/common' +import { + FlexRender, + TanStackTable, + TanStackTableCell, + TanStackTableHeader, +} from '@tanstack/angular-table' +import { makeProductData } from '../../makeData' +import { createAppColumnHelper, injectAppTable } from '../../table' +import type { Product } from '../../makeData' + +export const productColumnHelper = createAppColumnHelper() + +@Component({ + selector: 'products-table', + templateUrl: './products-table.html', + imports: [ + NgComponentOutlet, + FlexRender, + TanStackTable, + TanStackTableHeader, + TanStackTableCell, + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ProductsTable { + readonly data = signal(makeProductData(1_000)) + + readonly columns = productColumnHelper.columns([ + productColumnHelper.accessor('name', { + header: 'Product Name', + footer: (props) => props.column.id, + cell: ({ cell }) => cell.TextCell, + }), + productColumnHelper.accessor('category', { + header: 'Category', + footer: (props) => props.column.id, + cell: ({ cell }) => cell.CategoryCell, + }), + productColumnHelper.accessor('price', { + header: 'Price', + footer: (props) => props.column.id, + cell: ({ cell }) => cell.PriceCell, + }), + productColumnHelper.accessor('stock', { + header: 'In Stock', + footer: (props) => props.column.id, + cell: ({ cell }) => cell.NumberCell, + }), + productColumnHelper.accessor('rating', { + header: 'Rating', + footer: (props) => props.column.id, + cell: ({ cell }) => cell.ProgressCell, + }), + ]) + + table = injectAppTable(() => ({ + columns: this.columns, + data: this.data(), + getRowId: (row) => row.id, + // more table options + })) + + onRefresh = () => { + this.data.set([...makeProductData(1_000)]) + } + + refreshData = () => this.data.set(makeProductData(1_000)) + stressTest = () => this.data.set(makeProductData(200_000)) +} diff --git a/examples/angular/composable-tables/src/app/components/table-components.ts b/examples/angular/composable-tables/src/app/components/table-components.ts new file mode 100644 index 0000000000..d13783f270 --- /dev/null +++ b/examples/angular/composable-tables/src/app/components/table-components.ts @@ -0,0 +1,130 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + input, +} from '@angular/core' +import { injectTableContext } from '../table' + +@Component({ + selector: 'app-table-toolbar', + template: ` +
+

{{ title() }}

+
+ @if (onRefresh(); as onRefresh) { + + } + @if (onStressTest(); as onStressTest) { + + } + + +
+
+ `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TableToolbar { + readonly title = input.required() + readonly onRefresh = input<() => void>() + readonly onStressTest = input<() => void>() + + readonly table = injectTableContext() + + constructor() { + this.table().resetColumnFilters() + } +} + +@Component({ + selector: 'app-row-count', + template: ` +
Showing {{ length() }} of {{ rowCount() }} rows
+ `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class RowCount { + readonly table = injectTableContext() + + readonly length = computed(() => + this.table().getRowModel().rows.length.toLocaleString(), + ) + + readonly rowCount = computed(() => + this.table().getRowCount().toLocaleString(), + ) +} + +/** + * Pagination controls for the table + */ +@Component({ + selector: 'app-pagination-controls', + template: ` + + `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class PaginationControls { + readonly table = injectTableContext() + + readonly pageSizes = [10, 20, 30, 40, 50] + + readonly canPreviousPage = computed(() => this.table().getCanPreviousPage()) + readonly canNextPage = computed(() => this.table().getCanNextPage()) + readonly pageIndex = computed( + () => this.table().store.state.pagination.pageIndex, + ) + readonly pageSize = computed( + () => this.table().store.state.pagination.pageSize, + ) + readonly pageCount = computed(() => + this.table().getPageCount().toLocaleString(), + ) + + onPageChange(event: Event) { + const target = event.target as HTMLInputElement + const page = target.value ? Number(target.value) - 1 : 0 + this.table().setPageIndex(page) + } + + onPageSizeChange(event: Event) { + const target = event.target as HTMLSelectElement + this.table().setPageSize(Number(target.value)) + } +} diff --git a/examples/angular/composable-tables/src/app/components/users-table/users-table.html b/examples/angular/composable-tables/src/app/components/users-table/users-table.html new file mode 100644 index 0000000000..3744b5b0f7 --- /dev/null +++ b/examples/angular/composable-tables/src/app/components/users-table/users-table.html @@ -0,0 +1,72 @@ +
+ + + + + @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { + + @for (_header of headerGroup.headers; track _header.id) { + @let header = table.appHeader(_header); + @if (!header.isPlaceholder) { + + } + } + + } + + + @for (row of table.getRowModel().rows; track row.id) { + + @for (cell of row.getAllCells(); track cell.id) { + + } + + } + + + + @for (footerGroup of table.getFooterGroups(); track footerGroup.id) { + + @for (footer of footerGroup.headers; track footer.id) { + + } + + } + +
+ + {{ header }} + + + +
+
+ + +
+
+
+ + {{ cell }} + +
+ @if (!footer.isPlaceholder) { + + {{ footer }} + + } +
+ + + + +
diff --git a/examples/angular/composable-tables/src/app/components/users-table/users-table.ts b/examples/angular/composable-tables/src/app/components/users-table/users-table.ts new file mode 100644 index 0000000000..b0a150513a --- /dev/null +++ b/examples/angular/composable-tables/src/app/components/users-table/users-table.ts @@ -0,0 +1,82 @@ +import { ChangeDetectionStrategy, Component, signal } from '@angular/core' +import { NgComponentOutlet } from '@angular/common' +import { + FlexRender, + TanStackTable, + TanStackTableCell, + TanStackTableHeader, + flexRenderComponent, +} from '@tanstack/angular-table' +import { makeData } from '../../makeData' +import { createAppColumnHelper, injectAppTable } from '../../table' +import type { Person } from '../../makeData' + +export const personColumnHelper = createAppColumnHelper() + +@Component({ + selector: 'users-table', + templateUrl: './users-table.html', + imports: [ + NgComponentOutlet, + FlexRender, + TanStackTable, + TanStackTableHeader, + TanStackTableCell, + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class UsersTable { + readonly data = signal(makeData(1_000)) + + readonly columns = personColumnHelper.columns([ + personColumnHelper.accessor('firstName', { + header: 'First Name', + footer: ({ header }) => flexRenderComponent(header.FooterColumnId), + cell: ({ cell }) => flexRenderComponent(cell.TextCell), + }), + personColumnHelper.accessor('lastName', { + header: 'Last Name', + footer: ({ header }) => flexRenderComponent(header.FooterColumnId), + cell: ({ cell }) => flexRenderComponent(cell.TextCell), + }), + personColumnHelper.accessor('age', { + header: 'Age', + footer: ({ header }) => flexRenderComponent(header.FooterSum), + cell: ({ cell }) => flexRenderComponent(cell.NumberCell), + }), + personColumnHelper.accessor('visits', { + header: 'Visits', + footer: ({ header }) => flexRenderComponent(header.FooterSum), + cell: ({ cell }) => flexRenderComponent(cell.NumberCell), + }), + personColumnHelper.accessor('status', { + header: 'Status', + footer: ({ header }) => flexRenderComponent(header.FooterColumnId), + cell: ({ cell }) => flexRenderComponent(cell.StatusCell), + }), + personColumnHelper.accessor('progress', { + header: 'Progress', + footer: ({ header }) => flexRenderComponent(header.FooterSum), + cell: ({ cell }) => flexRenderComponent(cell.ProgressCell), + }), + personColumnHelper.display({ + id: 'actions', + header: 'Actions', + cell: ({ cell }) => flexRenderComponent(cell.RowActionsCell), + }), + ]) + + table = injectAppTable(() => ({ + columns: this.columns, + data: this.data(), + debugTable: true, + // more table options + })) + + onRefresh = () => { + this.data.set([...makeData(1_000)]) + } + + refreshData = () => this.data.set(makeData(1_000)) + stressTest = () => this.data.set(makeData(200_000)) +} diff --git a/examples/angular/composable-tables/src/app/makeData.ts b/examples/angular/composable-tables/src/app/makeData.ts new file mode 100644 index 0000000000..17dec1c6de --- /dev/null +++ b/examples/angular/composable-tables/src/app/makeData.ts @@ -0,0 +1,77 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +export type Product = { + id: string + name: string + category: 'electronics' | 'clothing' | 'food' | 'books' + price: number + stock: number + rating: number +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +const newProduct = (): Product => { + return { + id: faker.string.uuid(), + name: faker.commerce.productName(), + category: faker.helpers.shuffle([ + 'electronics', + 'clothing', + 'food', + 'books', + ])[0], + price: parseFloat(faker.commerce.price({ min: 5, max: 500 })), + stock: faker.number.int({ min: 0, max: 200 }), + rating: faker.number.int({ min: 0, max: 100 }), + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} + +export function makeProductData(count: number): Array { + return range(count).map(() => newProduct()) +} diff --git a/examples/angular/composable-tables/src/app/table.ts b/examples/angular/composable-tables/src/app/table.ts new file mode 100644 index 0000000000..c898eb0a92 --- /dev/null +++ b/examples/angular/composable-tables/src/app/table.ts @@ -0,0 +1,107 @@ +/** + * Custom table hook setup using createTableHook + * + * This file creates a custom useAppTable hook with pre-bound components. + * Features, row models, and default options are defined once here and shared across all tables. + * Context hooks and a pre-bound createAppColumnHelper are also exported. + */ +import { + columnFilteringFeature, + createFilteredRowModel, + createPaginatedRowModel, + createSortedRowModel, + createTableHook, + filterFns, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/angular-table' + +// Import table-level components +import { + PaginationControls, + RowCount, + TableToolbar, +} from './components/table-components' +// Import cell-level components +import { + CategoryCell, + NumberCell, + PriceCell, + ProgressCell, + RowActionsCell, + StatusCell, + TextCell, +} from './components/cell-components' +// Import header-level components (both use injectTableHeaderContext()) +import { + ColumnFilter, + FooterColumnId, + FooterSum, + SortIndicator, +} from './components/header-components' + +/** + * Create the custom table hook with all pre-bound components. + * This exports: + * - createAppColumnHelper: Create column definitions with TFeatures already bound + * - injectAppTable: Function for creating tables with TFeatures baked in + * - injectTableContext: Access table instance in tableComponents + * - injectTableCellContext: Access cell instance in cellComponents + * - injectTableHeaderContext: Access header instance in headerComponents + * - injectFlexRenderHeaderContext: Access FlexRenderContext with header-level typings + * - injectFlexRenderCellContext: Access FlexRenderContext with cell-level typings + */ +export const { + createAppColumnHelper, + injectAppTable, + injectTableContext, + injectTableCellContext, + injectTableHeaderContext, + // injectFlexRenderHeaderContext + // injectFlexRenderCellContext +} = createTableHook({ + // Features are set once here and shared across all tables + _features: tableFeatures({ + columnFilteringFeature, + rowPaginationFeature, + rowSortingFeature, + }), + + // Row models are set once here + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + }, + + // set any default table options here too + getRowId: (row) => row.id, + + // Register table-level components (accessible via table.ComponentName) + tableComponents: { + PaginationControls, + RowCount, + TableToolbar, + }, + + // Register cell-level components (accessible via cell.ComponentName in AppCell) + cellComponents: { + TextCell, + NumberCell, + ProgressCell, + StatusCell, + CategoryCell, + PriceCell, + RowActionsCell, + }, + + // Register header/footer-level components (accessible via header.ComponentName in AppHeader/AppFooter) + headerComponents: { + SortIndicator, + ColumnFilter, + FooterColumnId, + FooterSum, + }, +}) diff --git a/examples/angular/composable-tables/src/index.html b/examples/angular/composable-tables/src/index.html new file mode 100644 index 0000000000..2f15880fea --- /dev/null +++ b/examples/angular/composable-tables/src/index.html @@ -0,0 +1,13 @@ + + + + + Composable tables + + + + + + + + diff --git a/examples/angular/composable-tables/src/main.ts b/examples/angular/composable-tables/src/main.ts new file mode 100644 index 0000000000..8192dca694 --- /dev/null +++ b/examples/angular/composable-tables/src/main.ts @@ -0,0 +1,5 @@ +import { bootstrapApplication } from '@angular/platform-browser' +import { appConfig } from './app/app.config' +import { App } from './app/app' + +bootstrapApplication(App, appConfig).catch((err) => console.error(err)) diff --git a/examples/angular/composable-tables/src/styles.css b/examples/angular/composable-tables/src/styles.css new file mode 100644 index 0000000000..4d8fef0142 --- /dev/null +++ b/examples/angular/composable-tables/src/styles.css @@ -0,0 +1,605 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + width: 100%; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th, +td { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 8px 12px; + text-align: left; +} + +th { + background-color: #f5f5f5; + font-weight: 600; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +.table-container { + padding: 16px; + max-width: 1200px; + margin: 0 auto; + border-collapse: collapse; + border-spacing: 0; +} + +.pagination { + display: flex; + align-items: center; + gap: 8px; + margin-top: 16px; + flex-wrap: wrap; +} + +.pagination button { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px 8px; + cursor: pointer; + background: white; +} + +.pagination button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.pagination input { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px; + width: 64px; +} + +.pagination select { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px; +} + +.sort-indicator { + margin-left: 4px; +} + +.column-filter { + margin-top: 4px; +} + +.column-filter input { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px; + width: 100%; + font-size: 12px; +} + +.sortable-header { + cursor: pointer; + user-select: none; +} + +.sortable-header:hover { + background-color: #e8e8e8; +} + +.row-actions { + display: flex; + gap: 4px; +} + +.row-actions button { + border: 1px solid #ccc; + border-radius: 4px; + padding: 2px 8px; + cursor: pointer; + background: white; + font-size: 12px; +} + +.row-actions button:hover { + background-color: #f0f0f0; +} + +.status-badge { + display: inline-block; + padding: 2px 8px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; +} + +.status-badge.relationship { + background-color: #d4edda; + color: #155724; +} + +.status-badge.complicated { + background-color: #fff3cd; + color: #856404; +} + +.status-badge.single { + background-color: #cce5ff; + color: #004085; +} + +.progress-bar { + width: 100%; + height: 8px; + background-color: #e9ecef; + border-radius: 4px; + overflow: hidden; +} + +.progress-bar-fill { + height: 100%; + background-color: #007bff; + transition: width 0.2s; +} + +.table-toolbar { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + flex-wrap: wrap; + gap: 8px; + border-collapse: collapse; + border-spacing: 0; +} + +.table-toolbar h2 { + margin: 0; + border-collapse: collapse; + border-spacing: 0; +} + +.table-toolbar-actions { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.table-toolbar button { + border: 1px solid #ccc; + border-radius: 4px; + padding: 8px 16px; + cursor: pointer; + background: white; + border-collapse: collapse; + border-spacing: 0; +} + +.table-toolbar button:hover { + background-color: #f0f0f0; + border-collapse: collapse; + border-spacing: 0; +} + +.row-count { + color: #666; + font-size: 14px; + margin-top: 8px; +} + +.app { + padding: 16px; +} + +.app h1 { + text-align: center; + margin-bottom: 8px; +} + +.description { + text-align: center; + color: #666; + margin-bottom: 32px; +} + +.description code { + background-color: #f5f5f5; + padding: 2px 6px; + border-radius: 4px; + font-size: 13px; +} + +.table-divider { + height: 48px; + border-bottom: 1px solid #e0e0e0; + margin: 32px auto; + max-width: 1200px; + border-collapse: collapse; + border-spacing: 0; +} + +.category-badge { + display: inline-block; + padding: 2px 8px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; + text-transform: capitalize; +} + +.category-badge.electronics { + background-color: #e3f2fd; + color: #1565c0; +} + +.category-badge.clothing { + background-color: #fce4ec; + color: #c2185b; +} + +.category-badge.food { + background-color: #e8f5e9; + color: #2e7d32; +} + +.category-badge.books { + background-color: #fff8e1; + color: #f57c00; +} + +.price { + font-weight: 600; + color: #2e7d32; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/angular/composable-tables/tsconfig.app.json b/examples/angular/composable-tables/tsconfig.app.json new file mode 100644 index 0000000000..a0dcc37c60 --- /dev/null +++ b/examples/angular/composable-tables/tsconfig.app.json @@ -0,0 +1,11 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] +} diff --git a/examples/angular/composable-tables/tsconfig.json b/examples/angular/composable-tables/tsconfig.json new file mode 100644 index 0000000000..32789cdc2b --- /dev/null +++ b/examples/angular/composable-tables/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "isolatedModules": true, + "experimentalDecorators": true, + "importHelpers": true, + "target": "ES2022", + "module": "preserve" + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + }, + "include": ["**/*.ts", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/angular/basic/tsconfig.spec.json b/examples/angular/composable-tables/tsconfig.spec.json similarity index 100% rename from examples/angular/basic/tsconfig.spec.json rename to examples/angular/composable-tables/tsconfig.spec.json diff --git a/examples/angular/custom-plugin/.gitignore b/examples/angular/custom-plugin/.gitignore new file mode 100644 index 0000000000..854acd5fc0 --- /dev/null +++ b/examples/angular/custom-plugin/.gitignore @@ -0,0 +1,44 @@ +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out + +# Node +/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/mcp.json +.history/* + +# Miscellaneous +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings +__screenshots__/ + +# System files +.DS_Store +Thumbs.db diff --git a/examples/angular/custom-plugin/.vscode/extensions.json b/examples/angular/custom-plugin/.vscode/extensions.json new file mode 100644 index 0000000000..77b374577d --- /dev/null +++ b/examples/angular/custom-plugin/.vscode/extensions.json @@ -0,0 +1,4 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 + "recommendations": ["angular.ng-template"] +} diff --git a/examples/angular/custom-plugin/.vscode/launch.json b/examples/angular/custom-plugin/.vscode/launch.json new file mode 100644 index 0000000000..f30a599183 --- /dev/null +++ b/examples/angular/custom-plugin/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "ng serve", + "type": "chrome", + "request": "launch", + "preLaunchTask": "npm: start", + "url": "http://localhost:4200/" + } + ] +} diff --git a/examples/angular/custom-plugin/.vscode/mcp.json b/examples/angular/custom-plugin/.vscode/mcp.json new file mode 100644 index 0000000000..bf4004da94 --- /dev/null +++ b/examples/angular/custom-plugin/.vscode/mcp.json @@ -0,0 +1,9 @@ +{ + // For more information, visit: https://angular.dev/ai/mcp + "mcpServers": { + "angular-cli": { + "command": "npx", + "args": ["-y", "@angular/cli", "mcp"] + } + } +} diff --git a/examples/angular/custom-plugin/.vscode/tasks.json b/examples/angular/custom-plugin/.vscode/tasks.json new file mode 100644 index 0000000000..a471b2ac28 --- /dev/null +++ b/examples/angular/custom-plugin/.vscode/tasks.json @@ -0,0 +1,24 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "start", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "pattern": "$tsc", + "background": { + "activeOnStart": true, + "beginsPattern": { + "regexp": "Changes detected" + }, + "endsPattern": { + "regexp": "bundle generation (complete|failed)" + } + } + } + } + ] +} diff --git a/examples/angular/custom-plugin/README.md b/examples/angular/custom-plugin/README.md new file mode 100644 index 0000000000..7ba2b2ea74 --- /dev/null +++ b/examples/angular/custom-plugin/README.md @@ -0,0 +1,59 @@ +# CustomPlugin + +This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.1.0. + +## Development server + +To start a local development server, run: + +```bash +ng serve +``` + +Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files. + +## Code scaffolding + +Angular CLI includes powerful code scaffolding tools. To generate a new component, run: + +```bash +ng generate component component-name +``` + +For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run: + +```bash +ng generate --help +``` + +## Building + +To build the project run: + +```bash +ng build +``` + +This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed. + +## Running unit tests + +To execute unit tests with the [Vitest](https://vitest.dev/) test runner, use the following command: + +```bash +ng test +``` + +## Running end-to-end tests + +For end-to-end (e2e) testing, run: + +```bash +ng e2e +``` + +Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs. + +## Additional Resources + +For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. diff --git a/examples/angular/custom-plugin/angular.json b/examples/angular/custom-plugin/angular.json new file mode 100644 index 0000000000..5fc340228c --- /dev/null +++ b/examples/angular/custom-plugin/angular.json @@ -0,0 +1,99 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "cli": { + "packageManager": "pnpm", + "analytics": false, + "cache": { + "enabled": false + } + }, + "newProjectRoot": "projects", + "projects": { + "custom-plugin": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "inlineTemplate": true, + "inlineStyle": true, + "skipTests": true + }, + "@schematics/angular:class": { + "skipTests": true + }, + "@schematics/angular:directive": { + "skipTests": true + }, + "@schematics/angular:guard": { + "skipTests": true + }, + "@schematics/angular:interceptor": { + "skipTests": true + }, + "@schematics/angular:pipe": { + "skipTests": true + }, + "@schematics/angular:resolver": { + "skipTests": true + }, + "@schematics/angular:service": { + "skipTests": true + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular/build:application", + "options": { + "browser": "src/main.ts", + "tsConfig": "tsconfig.app.json", + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": ["src/styles.css"] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kB", + "maximumError": "8kB" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular/build:dev-server", + "configurations": { + "production": { + "buildTarget": "custom-plugin:build:production" + }, + "development": { + "buildTarget": "custom-plugin:build:development" + } + }, + "defaultConfiguration": "development" + } + } + } + } +} diff --git a/examples/angular/custom-plugin/package.json b/examples/angular/custom-plugin/package.json new file mode 100644 index 0000000000..ff867fcbd1 --- /dev/null +++ b/examples/angular/custom-plugin/package.json @@ -0,0 +1,30 @@ +{ + "name": "tanstack-angular-table-example-custom-plugin", + "scripts": { + "ng": "ng", + "start": "ng serve --port 5173", + "dev": "ng serve --port 5173", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "lint": "eslint ./src" + }, + "private": true, + "packageManager": "pnpm@11.0.9", + "dependencies": { + "@angular/common": "^21.2.12", + "@angular/compiler": "^21.2.12", + "@angular/core": "^21.2.12", + "@angular/forms": "^21.2.12", + "@angular/platform-browser": "^21.2.12", + "@angular/router": "^21.2.12", + "@tanstack/angular-table": "^9.0.0-alpha.45", + "rxjs": "~7.8.2", + "tslib": "^2.8.1" + }, + "devDependencies": { + "@angular/build": "^21.2.10", + "@angular/cli": "^21.2.10", + "@angular/compiler-cli": "^21.2.12", + "typescript": "6.0.3" + } +} diff --git a/examples/angular/row-selection/src/favicon.ico b/examples/angular/custom-plugin/public/favicon.ico similarity index 100% rename from examples/angular/row-selection/src/favicon.ico rename to examples/angular/custom-plugin/public/favicon.ico diff --git a/examples/angular/custom-plugin/src/app/app.config.ts b/examples/angular/custom-plugin/src/app/app.config.ts new file mode 100644 index 0000000000..d6d1974987 --- /dev/null +++ b/examples/angular/custom-plugin/src/app/app.config.ts @@ -0,0 +1,7 @@ +import { provideBrowserGlobalErrorListeners } from '@angular/core' + +import type { ApplicationConfig } from '@angular/core' + +export const appConfig: ApplicationConfig = { + providers: [provideBrowserGlobalErrorListeners()], +} diff --git a/examples/angular/custom-plugin/src/app/app.css b/examples/angular/custom-plugin/src/app/app.css new file mode 100644 index 0000000000..f898271575 --- /dev/null +++ b/examples/angular/custom-plugin/src/app/app.css @@ -0,0 +1,359 @@ +table { + border-collapse: collapse; + border-spacing: 0; + &[data-table-density] { + :where(td, th) { + transition: padding 0.2s; + padding: 16px; + } + + &[data-table-density='sm'] { + :where(td, th) { + padding: 4px; + } + } + + &[data-table-density='md'] { + :where(td, th) { + padding: 8px; + } + } + } +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/angular/custom-plugin/src/app/app.html b/examples/angular/custom-plugin/src/app/app.html new file mode 100644 index 0000000000..d9f557979d --- /dev/null +++ b/examples/angular/custom-plugin/src/app/app.html @@ -0,0 +1,53 @@ +
+ + +
+ + + + + @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { + + @for (header of headerGroup.headers; track header.id) { + @if (!header.isPlaceholder) { + + } + } + + } + + + @for (row of table.getRowModel().rows; track row.id) { + + @for (cell of row.getAllCells(); track cell.id) { + + } + + } + + + @for (footerGroup of table.getFooterGroups(); track footerGroup.id) { + + @for (footer of footerGroup.headers; track footer.id) { + + } + + } + +
+
+
+
+
+ {{ footer }} +
+
diff --git a/examples/angular/custom-plugin/src/app/app.ts b/examples/angular/custom-plugin/src/app/app.ts new file mode 100644 index 0000000000..0944e1f6e9 --- /dev/null +++ b/examples/angular/custom-plugin/src/app/app.ts @@ -0,0 +1,82 @@ +import { Component, signal } from '@angular/core' +import { + FlexRender, + createColumnHelper, + createPaginatedRowModel, + injectTable, + isFunction, + rowPaginationFeature, + tableFeatures, +} from '@tanstack/angular-table' +import { densityPlugin } from './density/density-feature' +import { makeData } from './makeData' +import type { DensityState } from './density/density-feature' +import type { Person } from './makeData' + +const _features = tableFeatures({ + rowPaginationFeature, + densityPlugin, // pass in our plugin just like any other stock feature +}) +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => 'Last Name', + footer: (props) => props.column.id, + }), + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + }), + columnHelper.accessor('visits', { + header: () => 'Visits', + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), +]) + +@Component({ + selector: 'app-root', + imports: [FlexRender], + templateUrl: './app.html', + styleUrl: './app.css', +}) +export class App { + readonly data = signal(makeData(20)) + readonly density = signal('md') + + readonly table = injectTable(() => ({ + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data: this.data(), + debugTable: true, + state: { + // passing the density state to the table, TS is still happy :) + density: this.density(), + }, + // using the new onDensityChange option, TS is still happy :) + onDensityChange: (updater) => + isFunction(updater) + ? this.density.update(updater) + : this.density.set(updater), + })) + + refreshData = () => this.data.set(makeData(20)) + stressTest = () => this.data.set(makeData(1_000)) +} diff --git a/examples/angular/custom-plugin/src/app/density/density-feature.ts b/examples/angular/custom-plugin/src/app/density/density-feature.ts new file mode 100644 index 0000000000..ac77f186ee --- /dev/null +++ b/examples/angular/custom-plugin/src/app/density/density-feature.ts @@ -0,0 +1,86 @@ +import { functionalUpdate, makeStateUpdater } from '@tanstack/angular-table' +import type { + OnChangeFn, + TableFeature, + TableFeatures, + Updater, +} from '@tanstack/angular-table' + +// TypeScript setup for our new feature with all of the same type-safety as stock TanStack Table features + +// define types for our new feature's custom state +export type DensityState = 'sm' | 'md' | 'lg' +export interface TableState_Density { + density: DensityState +} + +// define types for our new feature's table options +export interface TableOptions_Density { + enableDensity?: boolean + onDensityChange?: OnChangeFn +} + +// Define types for our new feature's table APIs +export interface Table_Density { + setDensity: (updater: Updater) => void + toggleDensity: (value?: DensityState) => void +} + +interface DensityPluginConstructors { + Table: Table_Density + TableOptions: TableOptions_Density + TableState: TableState_Density +} + +// Here is all of the actual javascript code for our new feature +export const densityPlugin: TableFeature< + DensityPluginConstructors +> = { + // define the new feature's initial state + getInitialState: (initialState) => { + return { + density: 'md', + ...initialState, // must come last + } + }, + + // define the new feature's default options + getDefaultTableOptions: (table) => { + return { + enableDensity: true, + onDensityChange: makeStateUpdater('density', table), + } + }, + // if you need to add a default column definition... + // getDefaultColumnDef: () => {}, + + // define the new feature's table instance methods + constructTableAPIs: (table) => { + table.setDensity = (updater) => { + const safeUpdater: Updater = (old) => { + const newState = functionalUpdate(updater, old) + return newState + } + return table.options.onDensityChange?.(safeUpdater) + } + table.toggleDensity = (value) => { + table.setDensity?.((old) => { + if (value) return value + return old === 'lg' ? 'md' : old === 'md' ? 'sm' : 'lg' // cycle through the 3 options + }) + } + }, + + // if you need to add row instance APIs... + // constructRowAPIs: (row) => {}, + + // if you need to add cell instance APIs... + // constructCellAPIs: (cell) => {}, + + // if you need to add column instance APIs... + // constructColumnAPIs: (column) => {}, + + // if you need to add header instance APIs... + // constructHeaderAPIs: (header) => {}, +} +// end of custom feature code diff --git a/examples/angular/custom-plugin/src/app/makeData.ts b/examples/angular/custom-plugin/src/app/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/angular/custom-plugin/src/app/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/angular/custom-plugin/src/index.html b/examples/angular/custom-plugin/src/index.html new file mode 100644 index 0000000000..ac0b0f3278 --- /dev/null +++ b/examples/angular/custom-plugin/src/index.html @@ -0,0 +1,13 @@ + + + + + CustomPlugin + + + + + + + + diff --git a/examples/angular/custom-plugin/src/main.ts b/examples/angular/custom-plugin/src/main.ts new file mode 100644 index 0000000000..8192dca694 --- /dev/null +++ b/examples/angular/custom-plugin/src/main.ts @@ -0,0 +1,5 @@ +import { bootstrapApplication } from '@angular/platform-browser' +import { appConfig } from './app/app.config' +import { App } from './app/app' + +bootstrapApplication(App, appConfig).catch((err) => console.error(err)) diff --git a/examples/angular/custom-plugin/src/styles.css b/examples/angular/custom-plugin/src/styles.css new file mode 100644 index 0000000000..f8ffbb8e90 --- /dev/null +++ b/examples/angular/custom-plugin/src/styles.css @@ -0,0 +1,373 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +tr { + border-bottom: 1px solid lightgray; +} + +button:disabled { + opacity: 0.5; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/angular/custom-plugin/tsconfig.app.json b/examples/angular/custom-plugin/tsconfig.app.json new file mode 100644 index 0000000000..a0dcc37c60 --- /dev/null +++ b/examples/angular/custom-plugin/tsconfig.app.json @@ -0,0 +1,11 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] +} diff --git a/examples/angular/custom-plugin/tsconfig.json b/examples/angular/custom-plugin/tsconfig.json new file mode 100644 index 0000000000..32789cdc2b --- /dev/null +++ b/examples/angular/custom-plugin/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "isolatedModules": true, + "experimentalDecorators": true, + "importHelpers": true, + "target": "ES2022", + "module": "preserve" + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + }, + "include": ["**/*.ts", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/angular/editable/.gitignore b/examples/angular/editable/.gitignore new file mode 100644 index 0000000000..854acd5fc0 --- /dev/null +++ b/examples/angular/editable/.gitignore @@ -0,0 +1,44 @@ +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out + +# Node +/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/mcp.json +.history/* + +# Miscellaneous +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings +__screenshots__/ + +# System files +.DS_Store +Thumbs.db diff --git a/examples/angular/editable/README.md b/examples/angular/editable/README.md new file mode 100644 index 0000000000..6235b17f36 --- /dev/null +++ b/examples/angular/editable/README.md @@ -0,0 +1,59 @@ +# Editable Data + +This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.1.0. + +## Development server + +To start a local development server, run: + +```bash +ng serve +``` + +Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files. + +## Code scaffolding + +Angular CLI includes powerful code scaffolding tools. To generate a new component, run: + +```bash +ng generate component component-name +``` + +For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run: + +```bash +ng generate --help +``` + +## Building + +To build the project run: + +```bash +ng build +``` + +This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed. + +## Running unit tests + +To execute unit tests with the [Vitest](https://vitest.dev/) test runner, use the following command: + +```bash +ng test +``` + +## Running end-to-end tests + +For end-to-end (e2e) testing, run: + +```bash +ng e2e +``` + +Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs. + +## Additional Resources + +For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. diff --git a/examples/angular/editable/angular.json b/examples/angular/editable/angular.json new file mode 100644 index 0000000000..13d3dd0545 --- /dev/null +++ b/examples/angular/editable/angular.json @@ -0,0 +1,99 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "cli": { + "packageManager": "pnpm", + "analytics": false, + "cache": { + "enabled": false + } + }, + "newProjectRoot": "projects", + "projects": { + "editable-data": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "inlineTemplate": true, + "inlineStyle": true, + "skipTests": true + }, + "@schematics/angular:class": { + "skipTests": true + }, + "@schematics/angular:directive": { + "skipTests": true + }, + "@schematics/angular:guard": { + "skipTests": true + }, + "@schematics/angular:interceptor": { + "skipTests": true + }, + "@schematics/angular:pipe": { + "skipTests": true + }, + "@schematics/angular:resolver": { + "skipTests": true + }, + "@schematics/angular:service": { + "skipTests": true + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular/build:application", + "options": { + "browser": "src/main.ts", + "tsConfig": "tsconfig.app.json", + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": ["src/styles.css"] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kB", + "maximumError": "8kB" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular/build:dev-server", + "configurations": { + "production": { + "buildTarget": "editable-data:build:production" + }, + "development": { + "buildTarget": "editable-data:build:development" + } + }, + "defaultConfiguration": "development" + } + } + } + } +} diff --git a/examples/angular/editable/package.json b/examples/angular/editable/package.json new file mode 100644 index 0000000000..7daae3345d --- /dev/null +++ b/examples/angular/editable/package.json @@ -0,0 +1,31 @@ +{ + "name": "tanstack-angular-table-example-editable", + "scripts": { + "ng": "ng", + "start": "ng serve --port 5173", + "dev": "ng serve --port 5173", + "build": "ng build", + "lint": "eslint ./src", + "watch": "ng build --watch --configuration development" + }, + "private": true, + "packageManager": "pnpm@11.0.9", + "dependencies": { + "@angular/common": "^21.2.12", + "@angular/compiler": "^21.2.12", + "@angular/core": "^21.2.12", + "@angular/forms": "^21.2.12", + "@angular/platform-browser": "^21.2.12", + "@angular/router": "^21.2.12", + "@faker-js/faker": "^10.4.0", + "@tanstack/angular-table": "^9.0.0-alpha.45", + "rxjs": "~7.8.2", + "tslib": "^2.8.1" + }, + "devDependencies": { + "@angular/build": "^21.2.10", + "@angular/cli": "^21.2.10", + "@angular/compiler-cli": "^21.2.12", + "typescript": "6.0.3" + } +} diff --git a/examples/angular/signal-input/src/favicon.ico b/examples/angular/editable/public/favicon.ico similarity index 100% rename from examples/angular/signal-input/src/favicon.ico rename to examples/angular/editable/public/favicon.ico diff --git a/examples/angular/editable/src/app/app.config.ts b/examples/angular/editable/src/app/app.config.ts new file mode 100644 index 0000000000..cbb47d366c --- /dev/null +++ b/examples/angular/editable/src/app/app.config.ts @@ -0,0 +1,6 @@ +import { provideBrowserGlobalErrorListeners } from '@angular/core' +import type { ApplicationConfig } from '@angular/core' + +export const appConfig: ApplicationConfig = { + providers: [provideBrowserGlobalErrorListeners()], +} diff --git a/examples/angular/editable/src/app/app.html b/examples/angular/editable/src/app/app.html new file mode 100644 index 0000000000..9e8c22936c --- /dev/null +++ b/examples/angular/editable/src/app/app.html @@ -0,0 +1,89 @@ +
+ + +
+ + + @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { + + @for (header of headerGroup.headers; track header.id) { + @if (!header.isPlaceholder) { + + } + } + + } + + + @for (row of table.getRowModel().rows; track row.id) { + + @for (cell of row.getAllCells(); track cell.id) { + + } + + } + + + @for (footerGroup of table.getFooterGroups(); track footerGroup.id) { + + @for (footer of footerGroup.headers; track footer.id) { + + } + + } + +
+ +
+
+
+ +
+
+
+ + {{ footer }} + +
+ +
+
+ + + + + +
Page
+ + {{ (table.store.state.pagination.pageIndex + 1).toLocaleString() }} of + {{ table.getPageCount().toLocaleString() }} + +
+
+
diff --git a/examples/angular/editable/src/app/app.ts b/examples/angular/editable/src/app/app.ts new file mode 100644 index 0000000000..d12ca1dbb0 --- /dev/null +++ b/examples/angular/editable/src/app/app.ts @@ -0,0 +1,133 @@ +import { + ChangeDetectionStrategy, + Component, + Injector, + afterNextRender, + inject, + signal, +} from '@angular/core' +import { + FlexRender, + createPaginatedRowModel, + flexRenderComponent, + injectTable, + rowPaginationFeature, + tableFeatures, +} from '@tanstack/angular-table' +import { EditableCell } from './editable-cell/editable-cell' +import { makeData } from './makeData' +import type { Person } from './makeData' +import type { ColumnDef, RowData, TableFeatures } from '@tanstack/angular-table' + +declare module '@tanstack/angular-table' { + interface TableMeta { + updateData: (rowIndex: number, columnId: string, value: unknown) => void + } +} + +const _features = tableFeatures({ + rowPaginationFeature, +}) + +const defaultColumn: Partial> = { + cell: ({ getValue, row, column, table }) => { + const initialValue = getValue() + + return flexRenderComponent(EditableCell, { + inputs: { + value: initialValue, + }, + outputs: { + change: (value) => { + if (table.options.meta?.updateData) { + table.options.meta.updateData(row.index, column.id, value) + } + }, + }, + }) + }, +} + +const defaultColumns: Array> = [ + { + accessorKey: 'firstName', + footer: (info) => info.column.id, + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + header: () => `Last Name`, + footer: (info) => info.column.id, + }, + { + accessorKey: 'age', + header: () => 'Age', + footer: (info) => info.column.id, + }, + { + accessorKey: 'visits', + header: () => `Visits`, + footer: (info) => info.column.id, + }, + { + accessorKey: 'status', + header: 'Status', + footer: (info) => info.column.id, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + footer: (info) => info.column.id, + }, +] + +@Component({ + selector: 'app-root', + imports: [FlexRender], + templateUrl: './app.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class App { + readonly data = signal>(makeData(10_000)) + readonly injector = inject(Injector) + + readonly autoResetPageIndex = signal(true) + + readonly table = injectTable(() => ({ + data: this.data(), + columns: defaultColumns, + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + }, + defaultColumn: defaultColumn, + debugTable: true, + autoResetPageIndex: this.autoResetPageIndex(), + // Provide our updateData function to our table meta + meta: { + updateData: (rowIndex, columnId, value) => { + // Skip page index reset until after next rerender + this.autoResetPageIndex.set(false) + + this.data.update((old) => + old.map((row, index) => { + if (index === rowIndex) { + return { + ...old[rowIndex], + [columnId]: value, + } + } + return row + }), + ) + + afterNextRender(() => this.autoResetPageIndex.set(true), { + injector: this.injector, + }) + }, + }, + })) + + refreshData = () => this.data.set(makeData(10_000)) + stressTest = () => this.data.set(makeData(200_000)) +} diff --git a/examples/angular/editable/src/app/editable-cell/editable-cell.ts b/examples/angular/editable/src/app/editable-cell/editable-cell.ts new file mode 100644 index 0000000000..7d2e1e131a --- /dev/null +++ b/examples/angular/editable/src/app/editable-cell/editable-cell.ts @@ -0,0 +1,19 @@ +import { Component, input, output } from '@angular/core' +import { FormsModule } from '@angular/forms' + +@Component({ + selector: 'editable-cell', + template: ` + + `, + imports: [FormsModule], +}) +export class EditableCell { + readonly value = input() + + readonly change = output() +} diff --git a/examples/angular/editable/src/app/makeData.ts b/examples/angular/editable/src/app/makeData.ts new file mode 100644 index 0000000000..b9fb014aba --- /dev/null +++ b/examples/angular/editable/src/app/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/angular/editable/src/index.html b/examples/angular/editable/src/index.html new file mode 100644 index 0000000000..50751db4e6 --- /dev/null +++ b/examples/angular/editable/src/index.html @@ -0,0 +1,13 @@ + + + + + Editable data + + + + + + + + diff --git a/examples/angular/editable/src/main.ts b/examples/angular/editable/src/main.ts new file mode 100644 index 0000000000..8192dca694 --- /dev/null +++ b/examples/angular/editable/src/main.ts @@ -0,0 +1,5 @@ +import { bootstrapApplication } from '@angular/platform-browser' +import { appConfig } from './app/app.config' +import { App } from './app/app' + +bootstrapApplication(App, appConfig).catch((err) => console.error(err)) diff --git a/examples/angular/editable/src/styles.css b/examples/angular/editable/src/styles.css new file mode 100644 index 0000000000..3546a66331 --- /dev/null +++ b/examples/angular/editable/src/styles.css @@ -0,0 +1,371 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +.pagination-actions { + margin: 10px; + display: flex; + gap: 10px; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/angular/editable/tsconfig.app.json b/examples/angular/editable/tsconfig.app.json new file mode 100644 index 0000000000..a0dcc37c60 --- /dev/null +++ b/examples/angular/editable/tsconfig.app.json @@ -0,0 +1,11 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] +} diff --git a/examples/angular/editable/tsconfig.json b/examples/angular/editable/tsconfig.json new file mode 100644 index 0000000000..32789cdc2b --- /dev/null +++ b/examples/angular/editable/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "isolatedModules": true, + "experimentalDecorators": true, + "importHelpers": true, + "target": "ES2022", + "module": "preserve" + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + }, + "include": ["**/*.ts", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/angular/expanding/.gitignore b/examples/angular/expanding/.gitignore new file mode 100644 index 0000000000..854acd5fc0 --- /dev/null +++ b/examples/angular/expanding/.gitignore @@ -0,0 +1,44 @@ +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out + +# Node +/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/mcp.json +.history/* + +# Miscellaneous +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings +__screenshots__/ + +# System files +.DS_Store +Thumbs.db diff --git a/examples/angular/expanding/README.md b/examples/angular/expanding/README.md new file mode 100644 index 0000000000..62711f7792 --- /dev/null +++ b/examples/angular/expanding/README.md @@ -0,0 +1,59 @@ +# Expanding + +This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.1.0. + +## Development server + +To start a local development server, run: + +```bash +ng serve +``` + +Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files. + +## Code scaffolding + +Angular CLI includes powerful code scaffolding tools. To generate a new component, run: + +```bash +ng generate component component-name +``` + +For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run: + +```bash +ng generate --help +``` + +## Building + +To build the project run: + +```bash +ng build +``` + +This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed. + +## Running unit tests + +To execute unit tests with the [Vitest](https://vitest.dev/) test runner, use the following command: + +```bash +ng test +``` + +## Running end-to-end tests + +For end-to-end (e2e) testing, run: + +```bash +ng e2e +``` + +Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs. + +## Additional Resources + +For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. diff --git a/examples/angular/expanding/angular.json b/examples/angular/expanding/angular.json new file mode 100644 index 0000000000..9743569091 --- /dev/null +++ b/examples/angular/expanding/angular.json @@ -0,0 +1,99 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "cli": { + "packageManager": "pnpm", + "analytics": false, + "cache": { + "enabled": false + } + }, + "newProjectRoot": "projects", + "projects": { + "expanding": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "inlineTemplate": true, + "inlineStyle": true, + "skipTests": true + }, + "@schematics/angular:class": { + "skipTests": true + }, + "@schematics/angular:directive": { + "skipTests": true + }, + "@schematics/angular:guard": { + "skipTests": true + }, + "@schematics/angular:interceptor": { + "skipTests": true + }, + "@schematics/angular:pipe": { + "skipTests": true + }, + "@schematics/angular:resolver": { + "skipTests": true + }, + "@schematics/angular:service": { + "skipTests": true + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular/build:application", + "options": { + "browser": "src/main.ts", + "tsConfig": "tsconfig.app.json", + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": ["src/styles.css"] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kB", + "maximumError": "8kB" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular/build:dev-server", + "configurations": { + "production": { + "buildTarget": "expanding:build:production" + }, + "development": { + "buildTarget": "expanding:build:development" + } + }, + "defaultConfiguration": "development" + } + } + } + } +} diff --git a/examples/angular/expanding/package.json b/examples/angular/expanding/package.json new file mode 100644 index 0000000000..19759464a5 --- /dev/null +++ b/examples/angular/expanding/package.json @@ -0,0 +1,31 @@ +{ + "name": "tanstack-angular-table-example-expanding", + "scripts": { + "ng": "ng", + "start": "ng serve --port 5173", + "dev": "ng serve --port 5173", + "build": "ng build", + "lint": "eslint ./src", + "watch": "ng build --watch --configuration development" + }, + "private": true, + "packageManager": "pnpm@11.0.9", + "dependencies": { + "@angular/common": "^21.2.12", + "@angular/compiler": "^21.2.12", + "@angular/core": "^21.2.12", + "@angular/forms": "^21.2.12", + "@angular/platform-browser": "^21.2.12", + "@angular/router": "^21.2.12", + "@faker-js/faker": "^10.4.0", + "@tanstack/angular-table": "^9.0.0-alpha.45", + "rxjs": "~7.8.2", + "tslib": "^2.8.1" + }, + "devDependencies": { + "@angular/build": "^21.2.10", + "@angular/cli": "^21.2.10", + "@angular/compiler-cli": "^21.2.12", + "typescript": "6.0.3" + } +} diff --git a/examples/angular/expanding/public/favicon.ico b/examples/angular/expanding/public/favicon.ico new file mode 100644 index 0000000000..57614f9c96 Binary files /dev/null and b/examples/angular/expanding/public/favicon.ico differ diff --git a/examples/angular/expanding/src/app/app.config.ts b/examples/angular/expanding/src/app/app.config.ts new file mode 100644 index 0000000000..cbb47d366c --- /dev/null +++ b/examples/angular/expanding/src/app/app.config.ts @@ -0,0 +1,6 @@ +import { provideBrowserGlobalErrorListeners } from '@angular/core' +import type { ApplicationConfig } from '@angular/core' + +export const appConfig: ApplicationConfig = { + providers: [provideBrowserGlobalErrorListeners()], +} diff --git a/examples/angular/expanding/src/app/app.html b/examples/angular/expanding/src/app/app.html new file mode 100644 index 0000000000..08f2f79685 --- /dev/null +++ b/examples/angular/expanding/src/app/app.html @@ -0,0 +1,107 @@ +
+ + +
+ +
+ + + @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { + + @for (header of headerGroup.headers; track header.id) { + + } + + } + + + @for (row of table.getRowModel().rows; track row.id) { + + @for (cell of row.getAllCells(); track cell.id) { + + } + + } + +
+ @if (!header.isPlaceholder) { + +
+
+ } +
+ +
+
+
+
+ +
+
+ + + + + +
Page
+ + {{ (table.store.state.pagination.pageIndex + 1).toLocaleString() }} of + {{ table.getPageCount().toLocaleString() }} + +
+ + | Go to page: + + + + +
+ + +
{{ rawExpandedState() }}
+ +
{{ rawRowSelectionState() }}
+
diff --git a/examples/angular/expanding/src/app/app.ts b/examples/angular/expanding/src/app/app.ts new file mode 100644 index 0000000000..8670a8e65b --- /dev/null +++ b/examples/angular/expanding/src/app/app.ts @@ -0,0 +1,131 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + signal, +} from '@angular/core' +import { + FlexRender, + createExpandedRowModel, + createPaginatedRowModel, + flexRenderComponent, + injectTable, + rowExpandingFeature, + rowPaginationFeature, + rowSelectionFeature, + shallow, + tableFeatures, +} from '@tanstack/angular-table' +import { ReactiveFormsModule } from '@angular/forms' +import { makeData } from './makeData' +import { + ExpandableCell, + ExpandableHeaderCell, +} from './expandable-cell/expandable-cell' +import type { Person } from './makeData' +import type { ColumnDef, ExpandedState } from '@tanstack/angular-table' + +export const _features = tableFeatures({ + rowExpandingFeature: rowExpandingFeature, + rowPaginationFeature: rowPaginationFeature, + rowSelectionFeature: rowSelectionFeature, +}) + +const defaultColumns: Array> = [ + { + accessorKey: 'firstName', + header: () => + flexRenderComponent(ExpandableHeaderCell, { + inputs: { + label: 'First name', + }, + }), + cell: () => flexRenderComponent(ExpandableCell), + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => info.getValue(), + header: () => 'Last Name', + footer: (props) => props.column.id, + }, + { + accessorKey: 'age', + header: () => 'Age', + footer: (props) => props.column.id, + }, + { + accessorKey: 'visits', + header: () => `Visits`, + footer: (props) => props.column.id, + }, + { + accessorKey: 'status', + header: 'Status', + footer: (props) => props.column.id, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + footer: (props) => props.column.id, + }, +] + +@Component({ + selector: 'app-root', + imports: [FlexRender, ReactiveFormsModule], + templateUrl: './app.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class App { + readonly data = signal>(makeData(100, 5, 3)) + readonly expanded = signal({}) + + readonly table = injectTable(() => ({ + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + expandedRowModel: createExpandedRowModel(), + }, + data: this.data(), + columns: defaultColumns, + state: { + expanded: this.expanded(), + }, + onExpandedChange: (updater) => + typeof updater === 'function' + ? this.expanded.update(updater) + : this.expanded.set(updater), + getSubRows: (row) => row.subRows, + // filterFromLeafRows: true, + // maxLeafRowFilterDepth: 0, + debugTable: true, + })) + + readonly rawExpandedState = computed(() => + JSON.stringify(this.expanded(), undefined, 2), + ) + + readonly rowSelectionState = computed( + () => this.table.atoms.rowSelection.get(), + { + equal: shallow, + }, + ) + readonly rawRowSelectionState = computed(() => + JSON.stringify(this.rowSelectionState(), undefined, 2), + ) + + onPageInputChange(event: Event): void { + const inputElement = event.target as HTMLInputElement + const page = inputElement.value ? Number(inputElement.value) - 1 : 0 + this.table.setPageIndex(page) + } + + onPageSizeChange(event: any): void { + this.table.setPageSize(Number(event.target.value)) + } + + refreshData = () => this.data.set(makeData(100, 5, 3)) + stressTest = () => this.data.set(makeData(10_000, 5, 3)) +} diff --git a/examples/angular/expanding/src/app/expandable-cell/expandable-cell.ts b/examples/angular/expanding/src/app/expandable-cell/expandable-cell.ts new file mode 100644 index 0000000000..281edc92d4 --- /dev/null +++ b/examples/angular/expanding/src/app/expandable-cell/expandable-cell.ts @@ -0,0 +1,79 @@ +import { ChangeDetectionStrategy, Component, input } from '@angular/core' +import { + injectTableCellContext, + injectTableHeaderContext, +} from '@tanstack/angular-table' +import type { RowData } from '@tanstack/angular-table' +import type { _features } from '../app' + +@Component({ + standalone: true, + template: ` + + {{ ' ' }} + + + + {{ label() }} + `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ExpandableHeaderCell { + readonly context = injectTableHeaderContext() + + readonly label = input.required() + + get table() { + return this.context().table + } +} + +@Component({ + standalone: true, + template: ` +
+
+ + {{ ' ' }} + + @if (row.getCanExpand()) { + + } @else { + 🔵 + } + {{ ' ' }} + + {{ context().getValue() }} +
+
+ `, + changeDetection: ChangeDetectionStrategy.OnPush, + styles: ` + :host { + > div { + padding-left: calc(2rem * var(--depth, 1)); + } + } + `, +}) +export class ExpandableCell { + readonly context = injectTableCellContext() + + get row() { + return this.context().row + } +} diff --git a/examples/angular/expanding/src/app/makeData.ts b/examples/angular/expanding/src/app/makeData.ts new file mode 100644 index 0000000000..b9fb014aba --- /dev/null +++ b/examples/angular/expanding/src/app/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/angular/expanding/src/index.html b/examples/angular/expanding/src/index.html new file mode 100644 index 0000000000..61c94c972f --- /dev/null +++ b/examples/angular/expanding/src/index.html @@ -0,0 +1,13 @@ + + + + + Expanding + + + + + + + + diff --git a/examples/angular/expanding/src/main.ts b/examples/angular/expanding/src/main.ts new file mode 100644 index 0000000000..8192dca694 --- /dev/null +++ b/examples/angular/expanding/src/main.ts @@ -0,0 +1,5 @@ +import { bootstrapApplication } from '@angular/platform-browser' +import { appConfig } from './app/app.config' +import { App } from './app/app' + +bootstrapApplication(App, appConfig).catch((err) => console.error(err)) diff --git a/examples/angular/expanding/src/styles.css b/examples/angular/expanding/src/styles.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/angular/expanding/src/styles.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/angular/expanding/tsconfig.app.json b/examples/angular/expanding/tsconfig.app.json new file mode 100644 index 0000000000..a0dcc37c60 --- /dev/null +++ b/examples/angular/expanding/tsconfig.app.json @@ -0,0 +1,11 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] +} diff --git a/examples/angular/expanding/tsconfig.json b/examples/angular/expanding/tsconfig.json new file mode 100644 index 0000000000..32789cdc2b --- /dev/null +++ b/examples/angular/expanding/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "isolatedModules": true, + "experimentalDecorators": true, + "importHelpers": true, + "target": "ES2022", + "module": "preserve" + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + }, + "include": ["**/*.ts", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/angular/filters/.devcontainer/devcontainer.json b/examples/angular/filters/.devcontainer/devcontainer.json deleted file mode 100644 index 36f47d8762..0000000000 --- a/examples/angular/filters/.devcontainer/devcontainer.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "Node.js", - "image": "mcr.microsoft.com/devcontainers/javascript-node:18" -} diff --git a/examples/angular/filters/.editorconfig b/examples/angular/filters/.editorconfig deleted file mode 100644 index 59d9a3a3e7..0000000000 --- a/examples/angular/filters/.editorconfig +++ /dev/null @@ -1,16 +0,0 @@ -# Editor configuration, see https://editorconfig.org -root = true - -[*] -charset = utf-8 -indent_style = space -indent_size = 2 -insert_final_newline = true -trim_trailing_whitespace = true - -[*.ts] -quote_type = single - -[*.md] -max_line_length = off -trim_trailing_whitespace = false diff --git a/examples/angular/filters/.gitignore b/examples/angular/filters/.gitignore index 0711527ef9..854acd5fc0 100644 --- a/examples/angular/filters/.gitignore +++ b/examples/angular/filters/.gitignore @@ -1,4 +1,4 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. # Compiled output /dist @@ -26,6 +26,7 @@ yarn-error.log !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json +!.vscode/mcp.json .history/* # Miscellaneous @@ -36,6 +37,7 @@ yarn-error.log /libpeerconnection.log testem.log /typings +__screenshots__/ # System files .DS_Store diff --git a/examples/angular/filters/README.md b/examples/angular/filters/README.md index 73a201f1eb..82235139a6 100644 --- a/examples/angular/filters/README.md +++ b/examples/angular/filters/README.md @@ -1,27 +1,59 @@ -# Selection +# Filters -This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.1.2. +This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.1.0. ## Development server -Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. +To start a local development server, run: + +```bash +ng serve +``` + +Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files. ## Code scaffolding -Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. +Angular CLI includes powerful code scaffolding tools. To generate a new component, run: + +```bash +ng generate component component-name +``` + +For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run: + +```bash +ng generate --help +``` -## Build +## Building -Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. +To build the project run: + +```bash +ng build +``` + +This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed. ## Running unit tests -Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). +To execute unit tests with the [Vitest](https://vitest.dev/) test runner, use the following command: + +```bash +ng test +``` ## Running end-to-end tests -Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. +For end-to-end (e2e) testing, run: + +```bash +ng e2e +``` + +Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs. -## Further help +## Additional Resources -To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. +For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. diff --git a/examples/angular/filters/angular.json b/examples/angular/filters/angular.json index 2c4d7a1d6c..01aaea497d 100644 --- a/examples/angular/filters/angular.json +++ b/examples/angular/filters/angular.json @@ -1,13 +1,43 @@ { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, + "cli": { + "packageManager": "pnpm", + "analytics": false, + "cache": { + "enabled": false + } + }, "newProjectRoot": "projects", "projects": { - "selection": { + "filters": { "projectType": "application", "schematics": { "@schematics/angular:component": { - "style": "scss" + "inlineTemplate": true, + "inlineStyle": true, + "skipTests": true + }, + "@schematics/angular:class": { + "skipTests": true + }, + "@schematics/angular:directive": { + "skipTests": true + }, + "@schematics/angular:guard": { + "skipTests": true + }, + "@schematics/angular:interceptor": { + "skipTests": true + }, + "@schematics/angular:pipe": { + "skipTests": true + }, + "@schematics/angular:resolver": { + "skipTests": true + }, + "@schematics/angular:service": { + "skipTests": true } }, "root": "", @@ -15,21 +45,32 @@ "prefix": "app", "architect": { "build": { - "builder": "@angular-devkit/build-angular:application", + "builder": "@angular/build:application", "options": { - "outputPath": "dist/filters", - "index": "src/index.html", "browser": "src/main.ts", - "polyfills": ["zone.js"], "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": ["src/favicon.ico", "src/assets"], - "styles": ["src/styles.scss"], - "scripts": [] + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": ["src/styles.css"] }, "configurations": { "production": { - "budgets": [], + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kB", + "maximumError": "8kB" + } + ], "outputHashing": "all" }, "development": { @@ -41,38 +82,18 @@ "defaultConfiguration": "production" }, "serve": { - "builder": "@angular-devkit/build-angular:dev-server", + "builder": "@angular/build:dev-server", "configurations": { "production": { - "buildTarget": "selection:build:production" + "buildTarget": "filters:build:production" }, "development": { - "buildTarget": "selection:build:development" + "buildTarget": "filters:build:development" } }, "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "buildTarget": "selection:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "polyfills": ["zone.js", "zone.js/testing"], - "tsConfig": "tsconfig.spec.json", - "inlineStyleLanguage": "scss", - "assets": ["src/favicon.ico", "src/assets"], - "styles": ["src/styles.scss"], - "scripts": [] - } } } } - }, - "cli": { - "analytics": false } } diff --git a/examples/angular/filters/package.json b/examples/angular/filters/package.json index 3098179b3e..d452543a97 100644 --- a/examples/angular/filters/package.json +++ b/examples/angular/filters/package.json @@ -1,39 +1,32 @@ { - "name": "tanstack-table-example-angular-filters", - "version": "0.0.0", + "name": "tanstack-angular-table-example-filters", "scripts": { "ng": "ng", - "start": "ng serve", + "start": "ng serve --port 5173", + "dev": "ng serve --port 5173", "build": "ng build", - "watch": "ng build --watch --configuration development", - "test": "ng test" + "lint": "eslint ./src", + "watch": "ng build --watch --configuration development" }, "private": true, + "packageManager": "pnpm@11.0.9", "dependencies": { - "@angular/animations": "^17.3.9", - "@angular/common": "^17.3.9", - "@angular/compiler": "^17.3.9", - "@angular/core": "^17.3.9", - "@angular/forms": "^17.3.9", - "@angular/platform-browser": "^17.3.9", - "@angular/platform-browser-dynamic": "^17.3.9", - "@faker-js/faker": "^8.4.1", - "@tanstack/angular-table": "^8.20.5", - "rxjs": "~7.8.1", - "tslib": "^2.6.2", - "zone.js": "~0.14.4" + "@angular/common": "^21.2.12", + "@angular/compiler": "^21.2.12", + "@angular/core": "^21.2.12", + "@angular/forms": "^21.2.12", + "@angular/platform-browser": "^21.2.12", + "@angular/router": "^21.2.12", + "@faker-js/faker": "^10.4.0", + "@tanstack/angular-pacer": "^0.23.0", + "@tanstack/angular-table": "^9.0.0-alpha.45", + "rxjs": "~7.8.2", + "tslib": "^2.8.1" }, "devDependencies": { - "@angular-devkit/build-angular": "^17.3.8", - "@angular/cli": "^17.3.8", - "@angular/compiler-cli": "^17.3.9", - "@types/jasmine": "~5.1.4", - "jasmine-core": "~5.1.2", - "karma": "~6.4.3", - "karma-chrome-launcher": "~3.2.0", - "karma-coverage": "~2.2.1", - "karma-jasmine": "~5.1.0", - "karma-jasmine-html-reporter": "~2.1.0", - "typescript": "5.4.5" + "@angular/build": "^21.2.10", + "@angular/cli": "^21.2.10", + "@angular/compiler-cli": "^21.2.12", + "typescript": "6.0.3" } } diff --git a/examples/angular/filters/public/favicon.ico b/examples/angular/filters/public/favicon.ico new file mode 100644 index 0000000000..57614f9c96 Binary files /dev/null and b/examples/angular/filters/public/favicon.ico differ diff --git a/examples/angular/filters/src/app/app.component.html b/examples/angular/filters/src/app/app.component.html deleted file mode 100644 index e121dbab64..0000000000 --- a/examples/angular/filters/src/app/app.component.html +++ /dev/null @@ -1,138 +0,0 @@ -
-
- - - - @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { - - @for (header of headerGroup.headers; track header.id) { - - } - - } - - - @for (row of table.getRowModel().rows; track row.id) { - - @for (cell of row.getVisibleCells(); track cell.id) { - - } - - } - -
- @if (!header.isPlaceholder) { -
- - {{ headerCell }} - - - @if (header.column.getIsSorted() === 'asc') { - 🔼 - } - @if (header.column.getIsSorted() === 'desc') { - 🔽 - } -
- - @if (header.column.getCanFilter()) { -
- -
- } - } -
- - {{ renderCell }} - -
- -
-
- - - - - -
Page
- - {{ table.getState().pagination.pageIndex + 1 }} of - {{ table.getPageCount() }} - -
- - | Go to page: - - - - -
-
{{ table.getPrePaginationRowModel().rows.length }} Rows
-
- -
-
-
{{ stringifiedFilters() }}
-
-
- - - Age 🥳 - diff --git a/examples/angular/filters/src/app/app.component.ts b/examples/angular/filters/src/app/app.component.ts deleted file mode 100644 index 1748a57674..0000000000 --- a/examples/angular/filters/src/app/app.component.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - computed, - signal, -} from '@angular/core' -import { - ColumnDef, - type ColumnFiltersState, - createAngularTable, - FlexRenderDirective, - getCoreRowModel, - getFacetedMinMaxValues, - getFacetedRowModel, - getFacetedUniqueValues, - getFilteredRowModel, - getPaginationRowModel, - getSortedRowModel, -} from '@tanstack/angular-table' -import { FilterComponent } from './table-filter.component' -import { makeData, type Person } from './makeData' -import { FormsModule } from '@angular/forms' -import { NgClass } from '@angular/common' - -@Component({ - selector: 'app-root', - standalone: true, - imports: [FilterComponent, FlexRenderDirective, FormsModule, NgClass], - templateUrl: './app.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AppComponent { - readonly columnFilters = signal([]) - readonly data = signal(makeData(5000)) - - readonly columns: ColumnDef[] = [ - { - accessorKey: 'firstName', - cell: info => info.getValue(), - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - cell: info => info.getValue(), - header: () => 'Last Name', - }, - { - accessorKey: 'age', - header: () => 'Age', - meta: { - filterVariant: 'range', - }, - }, - { - accessorKey: 'visits', - header: () => 'Visits', - meta: { - filterVariant: 'range', - }, - }, - { - accessorKey: 'status', - header: 'Status', - meta: { - filterVariant: 'select', - }, - }, - { - accessorKey: 'progress', - header: 'Profile Progress', - meta: { - filterVariant: 'range', - }, - }, - ] - - table = createAngularTable(() => ({ - columns: this.columns, - data: this.data(), - state: { - columnFilters: this.columnFilters(), - }, - onColumnFiltersChange: updater => { - updater instanceof Function - ? this.columnFilters.update(updater) - : this.columnFilters.set(updater) - }, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), //client-side filtering - getSortedRowModel: getSortedRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getFacetedRowModel: getFacetedRowModel(), // client-side faceting - getFacetedUniqueValues: getFacetedUniqueValues(), // generate unique values for select filter/autocomplete - getFacetedMinMaxValues: getFacetedMinMaxValues(), // generate min/max values for range filter - debugTable: true, - debugHeaders: true, - debugColumns: false, - })) - - readonly stringifiedFilters = computed(() => - JSON.stringify(this.columnFilters(), null, 2) - ) - - onPageInputChange(event: Event): void { - const inputElement = event.target as HTMLInputElement - const page = inputElement.value ? Number(inputElement.value) - 1 : 0 - this.table.setPageIndex(page) - } - - onPageSizeChange(event: any): void { - this.table.setPageSize(Number(event.target.value)) - } - - refreshData(): void { - this.data.set(makeData(100_000)) // stress test - } -} diff --git a/examples/angular/filters/src/app/app.config.ts b/examples/angular/filters/src/app/app.config.ts index f27099f33c..cbb47d366c 100644 --- a/examples/angular/filters/src/app/app.config.ts +++ b/examples/angular/filters/src/app/app.config.ts @@ -1,5 +1,6 @@ -import { ApplicationConfig } from '@angular/core' +import { provideBrowserGlobalErrorListeners } from '@angular/core' +import type { ApplicationConfig } from '@angular/core' export const appConfig: ApplicationConfig = { - providers: [], + providers: [provideBrowserGlobalErrorListeners()], } diff --git a/examples/angular/filters/src/app/app.html b/examples/angular/filters/src/app/app.html new file mode 100644 index 0000000000..4a9c219806 --- /dev/null +++ b/examples/angular/filters/src/app/app.html @@ -0,0 +1,104 @@ +
+ + +
+ + + + @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { + + @for (header of headerGroup.headers; track header.id) { + + } + + } + + + @for (row of table.getRowModel().rows; track row.id) { + + @for (cell of row.getAllCells(); track cell.id) { + + } + + } + +
+ @if (!header.isPlaceholder) { +
+ + {{ headerCell }} + +
+ + @if (header.column.getCanFilter()) { +
+ +
+ } + } +
+ + {{ renderCell }} + +
+ +
+
+ + + + + +
Page
+ + {{ (table.store.state.pagination.pageIndex + 1).toLocaleString() }} of + {{ table.getPageCount().toLocaleString() }} + +
+ + | Go to page: + + + + +
+
{{ table.getPrePaginatedRowModel().rows.length.toLocaleString() }} Rows
+
+
{{ stringifiedFilters() }}
+
+
diff --git a/examples/angular/filters/src/app/app.ts b/examples/angular/filters/src/app/app.ts new file mode 100644 index 0000000000..7cab3f551f --- /dev/null +++ b/examples/angular/filters/src/app/app.ts @@ -0,0 +1,123 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + signal, +} from '@angular/core' +import { + FlexRender, + columnFacetingFeature, + columnFilteringFeature, + createFacetedMinMaxValues, + createFacetedRowModel, + createFacetedUniqueValues, + createFilteredRowModel, + createPaginatedRowModel, + createTableHook, + filterFns, + isFunction, + rowPaginationFeature, + tableFeatures, +} from '@tanstack/angular-table' +import { makeData } from './makeData' +import { TableFilter } from './table-filter/table-filter' +import type { ColumnFiltersState, Updater } from '@tanstack/angular-table' +import type { Person } from './makeData' + +export const _features = tableFeatures({ + columnFilteringFeature, + columnFacetingFeature, + rowPaginationFeature, +}) + +const { injectAppTable, createAppColumnHelper } = createTableHook({ + _features, + _rowModels: { + facetedMinMaxValues: createFacetedMinMaxValues(), + facetedRowModel: createFacetedRowModel(), + facetedUniqueValues: createFacetedUniqueValues(), + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + }, + debugTable: true, + debugHeaders: true, + debugColumns: false, +}) + +const columnHelper = createAppColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => 'Last Name', + }), + columnHelper.accessor('age', { + header: () => 'Age', + meta: { + filterVariant: 'range', + }, + }), + columnHelper.accessor('visits', { + header: () => 'Visits', + meta: { + filterVariant: 'range', + }, + }), + columnHelper.accessor('status', { + header: 'Status', + meta: { + filterVariant: 'select', + }, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + meta: { + filterVariant: 'range', + }, + }), +]) + +@Component({ + selector: 'app-root', + imports: [TableFilter, FlexRender], + templateUrl: './app.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class App { + readonly columnFilters = signal([]) + readonly data = signal(makeData(1_000)) + + table = injectAppTable(() => ({ + columns, + data: this.data(), + state: { + columnFilters: this.columnFilters(), + }, + onColumnFiltersChange: (updater: Updater) => { + isFunction(updater) + ? this.columnFilters.update(updater) + : this.columnFilters.set(updater) + }, + })) + + readonly stringifiedFilters = computed(() => + JSON.stringify(this.columnFilters(), null, 2), + ) + + onPageInputChange(event: Event): void { + const inputElement = event.target as HTMLInputElement + const page = inputElement.value ? Number(inputElement.value) - 1 : 0 + this.table.setPageIndex(page) + } + + onPageSizeChange(event: any): void { + this.table.setPageSize(Number(event.target.value)) + } + + refreshData = () => this.data.set(makeData(1_000)) + stressTest = () => this.data.set(makeData(200_000)) +} diff --git a/examples/angular/filters/src/app/debounced-input.directive.ts b/examples/angular/filters/src/app/debounced-input.directive.ts deleted file mode 100644 index 19d5ef1b55..0000000000 --- a/examples/angular/filters/src/app/debounced-input.directive.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Directive, ElementRef, inject, input, NgZone } from '@angular/core' -import { - debounceTime, - fromEvent, - type MonoTypeOperatorFunction, - Observable, - switchMap, -} from 'rxjs' -import { outputFromObservable, toObservable } from '@angular/core/rxjs-interop' - -export function runOutsideAngular( - zone: NgZone -): MonoTypeOperatorFunction { - return source => - new Observable(subscriber => - zone.runOutsideAngular(() => source.subscribe(subscriber)) - ) -} - -@Directive({ - standalone: true, - selector: 'input[debouncedInput]', -}) -export class DebouncedInputDirective { - #ref = inject(ElementRef).nativeElement as HTMLInputElement - - readonly debounce = input(500) - readonly debounce$ = toObservable(this.debounce) - - readonly changeEvent = outputFromObservable( - this.debounce$.pipe( - switchMap(debounce => { - return fromEvent(this.#ref, 'change').pipe(debounceTime(debounce)) - }) - ) - ) -} diff --git a/examples/angular/filters/src/app/debounced-input/debounced-input.ts b/examples/angular/filters/src/app/debounced-input/debounced-input.ts new file mode 100644 index 0000000000..cc83285953 --- /dev/null +++ b/examples/angular/filters/src/app/debounced-input/debounced-input.ts @@ -0,0 +1,21 @@ +import { Directive, HostListener, input, output } from '@angular/core' +import { injectDebouncedCallback } from '@tanstack/angular-pacer' + +@Directive({ + standalone: true, + selector: 'input[debouncedInput]', +}) +export class DebouncedInput { + readonly debounce = input(500) + readonly changeEvent = output() + + readonly #emitChange = injectDebouncedCallback( + (event: Event) => this.changeEvent.emit(event), + { wait: () => this.debounce() }, + ) + + @HostListener('change', ['$event']) + onChange(event: Event) { + this.#emitChange(event) + } +} diff --git a/examples/angular/filters/src/app/makeData.ts b/examples/angular/filters/src/app/makeData.ts index 331dd1eb19..b9fb014aba 100644 --- a/examples/angular/filters/src/app/makeData.ts +++ b/examples/angular/filters/src/app/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,13 +29,13 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] return range(len).map((d): Person => { return { ...newPerson(), diff --git a/examples/angular/filters/src/app/table-filter.component.ts b/examples/angular/filters/src/app/table-filter.component.ts deleted file mode 100644 index 2c897cfe7d..0000000000 --- a/examples/angular/filters/src/app/table-filter.component.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { CommonModule } from '@angular/common' -import { Component, computed, input, OnInit } from '@angular/core' -import type { Column, RowData, Table } from '@tanstack/angular-table' -import { DebouncedInputDirective } from './debounced-input.directive' - -declare module '@tanstack/angular-table' { - //allows us to define custom properties for our columns - interface ColumnMeta { - filterVariant?: 'text' | 'range' | 'select' - } -} - -@Component({ - selector: 'app-table-filter', - template: ` - @if (filterVariant() === 'range') { -
-
- - - -
-
-
- } @else if (filterVariant() === 'select') { - - } @else { - - @for (value of sortedUniqueValues(); track value) { - - } - - -
- } - `, - standalone: true, - imports: [CommonModule, DebouncedInputDirective], -}) -export class FilterComponent { - column = input.required>() - - table = input.required>() - - readonly filterVariant = computed(() => { - return (this.column().columnDef.meta ?? {}).filterVariant - }) - - readonly columnFilterValue = computed(() => - this.column().getFilterValue() - ) - - readonly minRangePlaceholder = computed(() => { - return `Min ${ - this.column().getFacetedMinMaxValues()?.[0] !== undefined - ? `(${this.column().getFacetedMinMaxValues()?.[0]})` - : '' - }` - }) - - readonly maxRangePlaceholder = computed(() => { - return `Max ${ - this.column().getFacetedMinMaxValues()?.[1] - ? `(${this.column().getFacetedMinMaxValues()?.[1]})` - : '' - }` - }) - - readonly sortedUniqueValues = computed(() => { - const filterVariant = this.filterVariant() - const column = this.column() - if (filterVariant === 'range') { - return [] - } - return Array.from(column.getFacetedUniqueValues().keys()) - .sort() - .slice(0, 5000) - }) - - readonly changeMinRangeValue = (event: Event) => { - const value = (event.target as HTMLInputElement).value - this.column().setFilterValue((old: [number, number]) => { - return [value, old?.[1]] - }) - } - - readonly changeMaxRangeValue = (event: Event) => { - const value = (event.target as HTMLInputElement).value - this.column().setFilterValue((old: [number, number]) => { - return [old?.[0], value] - }) - } -} diff --git a/examples/angular/filters/src/app/table-filter/table-filter.ts b/examples/angular/filters/src/app/table-filter/table-filter.ts new file mode 100644 index 0000000000..3eff55ffca --- /dev/null +++ b/examples/angular/filters/src/app/table-filter/table-filter.ts @@ -0,0 +1,150 @@ +import { Component, computed, input } from '@angular/core' +import { DebouncedInput } from '../debounced-input/debounced-input' +import type { _features } from '../app' +import type { Person } from '../makeData' +import type { + CellData, + Column, + RowData, + Table, + TableFeatures, +} from '@tanstack/angular-table' + +declare module '@tanstack/angular-table' { + // allows us to define custom properties for our columns + interface ColumnMeta< + TFeatures extends TableFeatures, + TData extends RowData, + TValue extends CellData = CellData, + > { + filterVariant?: 'text' | 'range' | 'select' + } +} + +@Component({ + selector: 'app-table-filter', + template: ` + @switch (filterVariant()) { + @case ('range') { +
+
+ + + +
+
+
+ } + @case ('select') { + + } + @default { + + @for (value of sortedUniqueValues(); track value) { + + } + + +
+ } + } + `, + imports: [DebouncedInput], +}) +export class TableFilter { + readonly column = input.required>() + + readonly table = input.required>() + + readonly filterVariant = computed(() => { + return (this.column().columnDef.meta ?? {}).filterVariant + }) + + readonly columnFilterValue = computed( + () => this.column().getFilterValue() as any, + ) + + readonly minRangePlaceholder = computed(() => { + return `Min ${ + this.column().getFacetedMinMaxValues()?.[0] !== undefined + ? `(${this.column().getFacetedMinMaxValues()?.[0]})` + : '' + }` + }) + + readonly maxRangePlaceholder = computed(() => { + return `Max ${ + this.column().getFacetedMinMaxValues()?.[1] + ? `(${this.column().getFacetedMinMaxValues()?.[1]})` + : '' + }` + }) + + readonly sortedUniqueValues = computed(() => { + const filterVariant = this.filterVariant() + const column = this.column() + if (filterVariant === 'range') { + return [] + } + return Array.from(column.getFacetedUniqueValues().keys()) + .sort() + .slice(0, 5000) + }) + + readonly changeMinRangeValue = (event: Event) => { + const value = (event.target as HTMLInputElement).value + this.column().setFilterValue((old?: [number, number]) => { + return [value, old?.[1]] + }) + } + + readonly changeMaxRangeValue = (event: Event) => { + const value = (event.target as HTMLInputElement).value + this.column().setFilterValue((old?: [number, number]) => { + return [old?.[0], value] + }) + } +} diff --git a/examples/angular/filters/src/assets/.gitkeep b/examples/angular/filters/src/assets/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/examples/angular/filters/src/index.html b/examples/angular/filters/src/index.html index 27917d2b28..4f40d38cef 100644 --- a/examples/angular/filters/src/index.html +++ b/examples/angular/filters/src/index.html @@ -6,7 +6,6 @@ - diff --git a/examples/angular/filters/src/main.ts b/examples/angular/filters/src/main.ts index 0c3b92057c..8192dca694 100644 --- a/examples/angular/filters/src/main.ts +++ b/examples/angular/filters/src/main.ts @@ -1,5 +1,5 @@ import { bootstrapApplication } from '@angular/platform-browser' import { appConfig } from './app/app.config' -import { AppComponent } from './app/app.component' +import { App } from './app/app' -bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err)) +bootstrapApplication(App, appConfig).catch((err) => console.error(err)) diff --git a/examples/angular/filters/src/styles.css b/examples/angular/filters/src/styles.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/angular/filters/src/styles.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/angular/filters/src/styles.scss b/examples/angular/filters/src/styles.scss deleted file mode 100644 index 43c09e0f6b..0000000000 --- a/examples/angular/filters/src/styles.scss +++ /dev/null @@ -1,26 +0,0 @@ -html { - font-family: sans-serif; - font-size: 14px; -} - -table { - border: 1px solid lightgray; -} - -tbody { - border-bottom: 1px solid lightgray; -} - -th { - border-bottom: 1px solid lightgray; - border-right: 1px solid lightgray; - padding: 2px 4px; -} - -tfoot { - color: gray; -} - -tfoot th { - font-weight: normal; -} diff --git a/examples/angular/filters/tsconfig.app.json b/examples/angular/filters/tsconfig.app.json index 84f1f992d2..a0dcc37c60 100644 --- a/examples/angular/filters/tsconfig.app.json +++ b/examples/angular/filters/tsconfig.app.json @@ -1,10 +1,11 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/app", "types": [] }, - "files": ["src/main.ts"], - "include": ["src/**/*.d.ts"] + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] } diff --git a/examples/angular/filters/tsconfig.json b/examples/angular/filters/tsconfig.json index fd2d87ac26..32789cdc2b 100644 --- a/examples/angular/filters/tsconfig.json +++ b/examples/angular/filters/tsconfig.json @@ -1,30 +1,23 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ { "compileOnSave": false, "compilerOptions": { - "outDir": "./dist/out-tsc", - "forceConsistentCasingInFileNames": true, "strict": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "skipLibCheck": true, - "esModuleInterop": true, - "sourceMap": true, - "declaration": false, + "isolatedModules": true, "experimentalDecorators": true, - "moduleResolution": "node", "importHelpers": true, "target": "ES2022", - "module": "ES2022", - "useDefineForClassFields": false, - "lib": ["ES2022", "dom"] + "module": "preserve" }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": true, "strictInputAccessModifiers": true, "strictTemplates": true - } + }, + "include": ["**/*.ts", "vite.config.js", "vite.config.ts"] } diff --git a/examples/angular/filters/tsconfig.spec.json b/examples/angular/filters/tsconfig.spec.json deleted file mode 100644 index 47e3dd7551..0000000000 --- a/examples/angular/filters/tsconfig.spec.json +++ /dev/null @@ -1,9 +0,0 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/spec", - "types": ["jasmine"] - }, - "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] -} diff --git a/examples/angular/grouping/.devcontainer/devcontainer.json b/examples/angular/grouping/.devcontainer/devcontainer.json deleted file mode 100644 index 36f47d8762..0000000000 --- a/examples/angular/grouping/.devcontainer/devcontainer.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "Node.js", - "image": "mcr.microsoft.com/devcontainers/javascript-node:18" -} diff --git a/examples/angular/grouping/.editorconfig b/examples/angular/grouping/.editorconfig deleted file mode 100644 index 59d9a3a3e7..0000000000 --- a/examples/angular/grouping/.editorconfig +++ /dev/null @@ -1,16 +0,0 @@ -# Editor configuration, see https://editorconfig.org -root = true - -[*] -charset = utf-8 -indent_style = space -indent_size = 2 -insert_final_newline = true -trim_trailing_whitespace = true - -[*.ts] -quote_type = single - -[*.md] -max_line_length = off -trim_trailing_whitespace = false diff --git a/examples/angular/grouping/.gitignore b/examples/angular/grouping/.gitignore index 0711527ef9..854acd5fc0 100644 --- a/examples/angular/grouping/.gitignore +++ b/examples/angular/grouping/.gitignore @@ -1,4 +1,4 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. # Compiled output /dist @@ -26,6 +26,7 @@ yarn-error.log !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json +!.vscode/mcp.json .history/* # Miscellaneous @@ -36,6 +37,7 @@ yarn-error.log /libpeerconnection.log testem.log /typings +__screenshots__/ # System files .DS_Store diff --git a/examples/angular/grouping/README.md b/examples/angular/grouping/README.md index 8f0c2d4c12..b68f9301ee 100644 --- a/examples/angular/grouping/README.md +++ b/examples/angular/grouping/README.md @@ -1,27 +1,59 @@ # Grouping -This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.1.2. +This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.1.0. ## Development server -Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. +To start a local development server, run: + +```bash +ng serve +``` + +Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files. ## Code scaffolding -Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. +Angular CLI includes powerful code scaffolding tools. To generate a new component, run: + +```bash +ng generate component component-name +``` + +For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run: + +```bash +ng generate --help +``` -## Build +## Building -Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. +To build the project run: + +```bash +ng build +``` + +This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed. ## Running unit tests -Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). +To execute unit tests with the [Vitest](https://vitest.dev/) test runner, use the following command: + +```bash +ng test +``` ## Running end-to-end tests -Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. +For end-to-end (e2e) testing, run: + +```bash +ng e2e +``` + +Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs. -## Further help +## Additional Resources -To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. +For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. diff --git a/examples/angular/grouping/angular.json b/examples/angular/grouping/angular.json index 9577b47c44..d12da8df78 100644 --- a/examples/angular/grouping/angular.json +++ b/examples/angular/grouping/angular.json @@ -1,13 +1,43 @@ { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, + "cli": { + "packageManager": "pnpm", + "analytics": false, + "cache": { + "enabled": false + } + }, "newProjectRoot": "projects", "projects": { "grouping": { "projectType": "application", "schematics": { "@schematics/angular:component": { - "style": "scss" + "inlineTemplate": true, + "inlineStyle": true, + "skipTests": true + }, + "@schematics/angular:class": { + "skipTests": true + }, + "@schematics/angular:directive": { + "skipTests": true + }, + "@schematics/angular:guard": { + "skipTests": true + }, + "@schematics/angular:interceptor": { + "skipTests": true + }, + "@schematics/angular:pipe": { + "skipTests": true + }, + "@schematics/angular:resolver": { + "skipTests": true + }, + "@schematics/angular:service": { + "skipTests": true } }, "root": "", @@ -15,21 +45,32 @@ "prefix": "app", "architect": { "build": { - "builder": "@angular-devkit/build-angular:application", + "builder": "@angular/build:application", "options": { - "outputPath": "dist/grouping", - "index": "src/index.html", "browser": "src/main.ts", - "polyfills": ["zone.js"], "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": ["src/favicon.ico", "src/assets"], - "styles": ["src/styles.scss"], - "scripts": [] + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": ["src/styles.css"] }, "configurations": { "production": { - "budgets": [], + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kB", + "maximumError": "8kB" + } + ], "outputHashing": "all" }, "development": { @@ -41,7 +82,7 @@ "defaultConfiguration": "production" }, "serve": { - "builder": "@angular-devkit/build-angular:dev-server", + "builder": "@angular/build:dev-server", "configurations": { "production": { "buildTarget": "grouping:build:production" @@ -51,28 +92,8 @@ } }, "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "buildTarget": "grouping:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "polyfills": ["zone.js", "zone.js/testing"], - "tsConfig": "tsconfig.spec.json", - "inlineStyleLanguage": "scss", - "assets": ["src/favicon.ico", "src/assets"], - "styles": ["src/styles.scss"], - "scripts": [] - } } } } - }, - "cli": { - "analytics": false } } diff --git a/examples/angular/grouping/package.json b/examples/angular/grouping/package.json index ce7b27c37e..c7e34b3102 100644 --- a/examples/angular/grouping/package.json +++ b/examples/angular/grouping/package.json @@ -1,39 +1,31 @@ { - "name": "tanstack-table-example-angular-grouping", - "version": "0.0.0", + "name": "tanstack-angular-table-example-grouping", "scripts": { "ng": "ng", - "start": "ng serve", + "start": "ng serve --port 5173", + "dev": "ng serve --port 5173", "build": "ng build", "watch": "ng build --watch --configuration development", - "test": "ng test" + "lint": "eslint ./src" }, "private": true, + "packageManager": "pnpm@11.0.9", "dependencies": { - "@angular/animations": "^17.3.9", - "@angular/common": "^17.3.9", - "@angular/compiler": "^17.3.9", - "@angular/core": "^17.3.9", - "@angular/forms": "^17.3.9", - "@angular/platform-browser": "^17.3.9", - "@angular/platform-browser-dynamic": "^17.3.9", - "@faker-js/faker": "^8.4.1", - "@tanstack/angular-table": "^8.20.5", - "rxjs": "~7.8.1", - "zone.js": "~0.14.4" + "@angular/common": "^21.2.12", + "@angular/compiler": "^21.2.12", + "@angular/core": "^21.2.12", + "@angular/forms": "^21.2.12", + "@angular/platform-browser": "^21.2.12", + "@angular/router": "^21.2.12", + "@faker-js/faker": "^10.4.0", + "@tanstack/angular-table": "^9.0.0-alpha.45", + "rxjs": "~7.8.2", + "tslib": "^2.8.1" }, "devDependencies": { - "@angular-devkit/build-angular": "^17.3.8", - "@angular/cli": "^17.3.8", - "@angular/compiler-cli": "^17.3.9", - "@types/jasmine": "~5.1.4", - "jasmine-core": "~5.1.2", - "karma": "~6.4.3", - "karma-chrome-launcher": "~3.2.0", - "karma-coverage": "~2.2.1", - "karma-jasmine": "~5.1.0", - "karma-jasmine-html-reporter": "~2.1.0", - "tslib": "^2.6.2", - "typescript": "5.4.5" + "@angular/build": "^21.2.10", + "@angular/cli": "^21.2.10", + "@angular/compiler-cli": "^21.2.12", + "typescript": "6.0.3" } } diff --git a/examples/angular/grouping/public/favicon.ico b/examples/angular/grouping/public/favicon.ico new file mode 100644 index 0000000000..57614f9c96 Binary files /dev/null and b/examples/angular/grouping/public/favicon.ico differ diff --git a/examples/angular/grouping/src/app/app.component.html b/examples/angular/grouping/src/app/app.component.html deleted file mode 100644 index 198a34644a..0000000000 --- a/examples/angular/grouping/src/app/app.component.html +++ /dev/null @@ -1,171 +0,0 @@ -
-
- - - @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { - - @for (header of headerGroup.headers; track header.id) { - - } - - } - - - @for (row of table.getRowModel().rows; track row.id) { - - @for (cell of row.getVisibleCells(); track cell.id) { - - } - - } - -
- @if (header.isPlaceholder) { - - } @else { -
- @if (header.column.getCanGroup()) { - - - } - - {{ header }} - -
- } -
- @if (cell.getIsGrouped()) { - - - - {{ cell }} - - {{ ' ' }}{{ row.subRows.length }} - } @else if (cell.getIsAggregated()) { - - - - {{ aggregatedCell }} - - } @else if (cell.getIsPlaceholder()) { - - } @else { - - - - {{ cell }} - - } -
- -
-
- - - - - -
Page
- - {{ table.getState().pagination.pageIndex + 1 }} of - {{ table.getPageCount() }} - -
- - | Go to page: - - - - -
-
{{ table.getRowModel().rows.length }} Rows
-
- -
-
{{ stringifiedGrouping() }}
-
diff --git a/examples/angular/grouping/src/app/app.component.ts b/examples/angular/grouping/src/app/app.component.ts deleted file mode 100644 index 524f3c152f..0000000000 --- a/examples/angular/grouping/src/app/app.component.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { CommonModule } from '@angular/common' -import { - ChangeDetectionStrategy, - Component, - computed, - signal, -} from '@angular/core' -import { - FlexRenderDirective, - GroupingState, - Updater, - createAngularTable, - getCoreRowModel, - getExpandedRowModel, - getFilteredRowModel, - getGroupedRowModel, - getPaginationRowModel, -} from '@tanstack/angular-table' -import { columns } from './columns' -import { makeData } from './makeData' - -@Component({ - selector: 'app-root', - standalone: true, - imports: [FlexRenderDirective, CommonModule], - templateUrl: './app.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AppComponent { - title = 'grouping' - data = signal(makeData(10000)) - grouping = signal([]) - - stringifiedGrouping = computed(() => JSON.stringify(this.grouping(), null, 2)) - - tableOptions = computed(() => ({ - data: this.data(), - columns: columns, - state: { - grouping: this.grouping(), - }, - onGroupingChange: (updaterOrValue: Updater) => { - const groupingState = - typeof updaterOrValue === 'function' - ? updaterOrValue([...this.grouping()]) - : updaterOrValue - this.grouping.set(groupingState) - }, - getExpandedRowModel: getExpandedRowModel(), - getGroupedRowModel: getGroupedRowModel(), - getCoreRowModel: getCoreRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getFilteredRowModel: getFilteredRowModel(), - debugTable: true, - })) - - table = createAngularTable(this.tableOptions) - - onPageInputChange(event: any): void { - const page = event.target.value ? Number(event.target.value) - 1 : 0 - this.table.setPageIndex(page) - } - - onPageSizeChange(event: any) { - this.table.setPageSize(Number(event.target.value)) - } - - refreshData() { - this.data.set(makeData(10000)) - } -} diff --git a/examples/angular/grouping/src/app/app.config.ts b/examples/angular/grouping/src/app/app.config.ts index f27099f33c..cbb47d366c 100644 --- a/examples/angular/grouping/src/app/app.config.ts +++ b/examples/angular/grouping/src/app/app.config.ts @@ -1,5 +1,6 @@ -import { ApplicationConfig } from '@angular/core' +import { provideBrowserGlobalErrorListeners } from '@angular/core' +import type { ApplicationConfig } from '@angular/core' export const appConfig: ApplicationConfig = { - providers: [], + providers: [provideBrowserGlobalErrorListeners()], } diff --git a/examples/angular/grouping/src/app/app.html b/examples/angular/grouping/src/app/app.html new file mode 100644 index 0000000000..22da76185c --- /dev/null +++ b/examples/angular/grouping/src/app/app.html @@ -0,0 +1,146 @@ +
+ + +
+ + + @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { + + @for (header of headerGroup.headers; track header.id) { + + } + + } + + + @for (row of table.getRowModel().rows; track row.id) { + + @for (cell of row.getAllCells(); track cell.id) { + + } + + } + +
+ @if (header.isPlaceholder) { + + } @else { +
+ @if (header.column.getCanGroup()) { + + + } + + {{ header }} + +
+ } +
+ @if (cell.getIsGrouped()) { + + + + {{ cell }} + + {{ ' ' }}{{ row.subRows.length.toLocaleString() }} + } @else if (cell.getIsAggregated()) { + + + + {{ aggregatedCell }} + + } @else if (cell.getIsPlaceholder()) { + } @else { + + + + {{ cell }} + + } +
+ +
+
+ + + + + +
Page
+ + {{ (table.store.state.pagination.pageIndex + 1).toLocaleString() }} of + {{ table.getPageCount().toLocaleString() }} + +
+ + | Go to page: + + + + +
+
{{ table.getRowModel().rows.length.toLocaleString() }} Rows
+
{{ stringifiedGrouping() }}
+
diff --git a/examples/angular/grouping/src/app/app.ts b/examples/angular/grouping/src/app/app.ts new file mode 100644 index 0000000000..9f5aa4aa07 --- /dev/null +++ b/examples/angular/grouping/src/app/app.ts @@ -0,0 +1,56 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + signal, +} from '@angular/core' +import { FlexRender, isFunction } from '@tanstack/angular-table' +import { columns, injectTable } from './columns' +import { makeData } from './makeData' +import type { GroupingState, Updater } from '@tanstack/angular-table' + +@Component({ + selector: 'app-root', + standalone: true, + imports: [FlexRender], + templateUrl: './app.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class App { + readonly data = signal(makeData(1_000)) + readonly grouping = signal([]) + + readonly stringifiedGrouping = computed(() => + JSON.stringify(this.grouping(), null, 2), + ) + + readonly table = injectTable(() => ({ + debugTable: true, + data: this.data(), + columns: columns, + initialState: { + pagination: { pageSize: 20, pageIndex: 0 }, + }, + state: { + grouping: this.grouping(), + }, + onGroupingChange: (updaterOrValue: Updater) => { + const groupingState = isFunction(updaterOrValue) + ? updaterOrValue([...this.grouping()]) + : updaterOrValue + this.grouping.set(groupingState) + }, + })) + + onPageInputChange(event: any): void { + const page = event.target.value ? Number(event.target.value) - 1 : 0 + this.table.setPageIndex(page) + } + + onPageSizeChange(event: any) { + this.table.setPageSize(Number(event.target.value)) + } + + refreshData = () => this.data.set(makeData(1_000)) + stressTest = () => this.data.set(makeData(200_000)) +} diff --git a/examples/angular/grouping/src/app/columns.ts b/examples/angular/grouping/src/app/columns.ts index ab35acdf4b..4fc55b81fb 100644 --- a/examples/angular/grouping/src/app/columns.ts +++ b/examples/angular/grouping/src/app/columns.ts @@ -1,71 +1,81 @@ -import { ColumnDef } from '@tanstack/angular-table' +import { + aggregationFns, + columnFilteringFeature, + columnGroupingFeature, + createExpandedRowModel, + createFilteredRowModel, + createGroupedRowModel, + createPaginatedRowModel, + createTableHook, + filterFns, + rowExpandingFeature, + rowPaginationFeature, +} from '@tanstack/angular-table' +import type { Person } from './makeData' -export type Person = { - firstName: string - lastName: string - age: number - visits: number - progress: number - status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] -} +export const { createAppColumnHelper, injectAppTable: injectTable } = + createTableHook({ + _features: { + columnGroupingFeature, + rowPaginationFeature, + columnFilteringFeature, + rowExpandingFeature, + }, + _rowModels: { + groupedRowModel: createGroupedRowModel(aggregationFns), + expandedRowModel: createExpandedRowModel(), + paginatedRowModel: createPaginatedRowModel(), + filteredRowModel: createFilteredRowModel(filterFns), + }, + }) +const columnHelper = createAppColumnHelper() -export const columns: ColumnDef[] = [ - { +export const columns = columnHelper.columns([ + columnHelper.group({ header: 'Name', - columns: [ - { - accessorKey: 'firstName', - header: 'First Name', - cell: info => info.getValue(), - /** - * override the value used for row grouping - * (otherwise, defaults to the value derived from accessorKey / accessorFn) - */ - getGroupingValue: row => `${row.firstName} ${row.lastName}`, - }, - { - accessorFn: row => row.lastName, + columns: columnHelper.columns([ + columnHelper.accessor('firstName', { + header: () => 'First Name', + cell: (info) => info.getValue(), + getGroupingValue: (row) => `${row.firstName} ${row.lastName}`, + }), + columnHelper.accessor((row) => row.lastName, { id: 'lastName', - header: () => `Last Name`, - cell: info => info.getValue(), - }, - ], - }, - { + header: () => 'Last Name', + cell: (info) => info.getValue(), + }), + ]), + }), + columnHelper.group({ header: 'Info', - columns: [ - { - accessorKey: 'age', + columns: columnHelper.columns([ + columnHelper.accessor('age', { header: () => 'Age', aggregatedCell: ({ getValue }) => Math.round(getValue() * 100) / 100, aggregationFn: 'median', - }, - { + }), + columnHelper.group({ header: 'More Info', - columns: [ - { - accessorKey: 'visits', + columns: columnHelper.columns([ + columnHelper.accessor('visits', { header: () => `Visits`, aggregationFn: 'sum', - // aggregatedCell: ({ getValue }) => getValue().toLocaleString(), - }, - { - accessorKey: 'status', + aggregatedCell: ({ getValue }) => getValue().toLocaleString(), + }), + columnHelper.accessor('status', { header: 'Status', - }, - { - accessorKey: 'progress', + }), + columnHelper.accessor('progress', { header: 'Profile Progress', cell: ({ getValue }) => Math.round(getValue() * 100) / 100 + '%', aggregationFn: 'mean', aggregatedCell: ({ getValue }) => Math.round(getValue() * 100) / 100 + '%', - }, - ], - }, - ], - }, -] + }), + ]), + }), + ]), + }), +]) diff --git a/examples/angular/grouping/src/app/makeData.ts b/examples/angular/grouping/src/app/makeData.ts index 331dd1eb19..b393b01622 100644 --- a/examples/angular/grouping/src/app/makeData.ts +++ b/examples/angular/grouping/src/app/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,13 +29,13 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! +export function makeData(...lens: Array): Array { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] return range(len).map((d): Person => { return { ...newPerson(), diff --git a/examples/angular/grouping/src/assets/.gitkeep b/examples/angular/grouping/src/assets/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/examples/angular/grouping/src/index.html b/examples/angular/grouping/src/index.html index af6a41707a..0661683763 100644 --- a/examples/angular/grouping/src/index.html +++ b/examples/angular/grouping/src/index.html @@ -6,11 +6,7 @@ - - + diff --git a/examples/angular/grouping/src/main.ts b/examples/angular/grouping/src/main.ts index 0c3b92057c..8192dca694 100644 --- a/examples/angular/grouping/src/main.ts +++ b/examples/angular/grouping/src/main.ts @@ -1,5 +1,5 @@ import { bootstrapApplication } from '@angular/platform-browser' import { appConfig } from './app/app.config' -import { AppComponent } from './app/app.component' +import { App } from './app/app' -bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err)) +bootstrapApplication(App, appConfig).catch((err) => console.error(err)) diff --git a/examples/angular/grouping/src/styles.css b/examples/angular/grouping/src/styles.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/angular/grouping/src/styles.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/angular/grouping/src/styles.scss b/examples/angular/grouping/src/styles.scss deleted file mode 100644 index 43c09e0f6b..0000000000 --- a/examples/angular/grouping/src/styles.scss +++ /dev/null @@ -1,26 +0,0 @@ -html { - font-family: sans-serif; - font-size: 14px; -} - -table { - border: 1px solid lightgray; -} - -tbody { - border-bottom: 1px solid lightgray; -} - -th { - border-bottom: 1px solid lightgray; - border-right: 1px solid lightgray; - padding: 2px 4px; -} - -tfoot { - color: gray; -} - -tfoot th { - font-weight: normal; -} diff --git a/examples/angular/grouping/tsconfig.app.json b/examples/angular/grouping/tsconfig.app.json index 84f1f992d2..a0dcc37c60 100644 --- a/examples/angular/grouping/tsconfig.app.json +++ b/examples/angular/grouping/tsconfig.app.json @@ -1,10 +1,11 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/app", "types": [] }, - "files": ["src/main.ts"], - "include": ["src/**/*.d.ts"] + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] } diff --git a/examples/angular/grouping/tsconfig.json b/examples/angular/grouping/tsconfig.json index 82c63d482a..32789cdc2b 100644 --- a/examples/angular/grouping/tsconfig.json +++ b/examples/angular/grouping/tsconfig.json @@ -1,29 +1,23 @@ { "compileOnSave": false, "compilerOptions": { - "outDir": "./dist/out-tsc", - "forceConsistentCasingInFileNames": true, "strict": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "skipLibCheck": true, - "esModuleInterop": true, - "sourceMap": true, - "declaration": false, + "isolatedModules": true, "experimentalDecorators": true, - "moduleResolution": "node", "importHelpers": true, "target": "ES2022", - "module": "ES2022", - "useDefineForClassFields": false, - "lib": ["ES2022", "dom"] + "module": "preserve" }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": true, "strictInputAccessModifiers": true, "strictTemplates": true - } + }, + "include": ["**/*.ts", "vite.config.js", "vite.config.ts"] } diff --git a/examples/angular/grouping/tsconfig.spec.json b/examples/angular/grouping/tsconfig.spec.json deleted file mode 100644 index 47e3dd7551..0000000000 --- a/examples/angular/grouping/tsconfig.spec.json +++ /dev/null @@ -1,9 +0,0 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/spec", - "types": ["jasmine"] - }, - "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] -} diff --git a/examples/angular/basic/.gitignore b/examples/angular/remote-data/.gitignore similarity index 100% rename from examples/angular/basic/.gitignore rename to examples/angular/remote-data/.gitignore diff --git a/examples/angular/basic/README.md b/examples/angular/remote-data/README.md similarity index 100% rename from examples/angular/basic/README.md rename to examples/angular/remote-data/README.md diff --git a/examples/angular/remote-data/angular.json b/examples/angular/remote-data/angular.json new file mode 100644 index 0000000000..39afd34914 --- /dev/null +++ b/examples/angular/remote-data/angular.json @@ -0,0 +1,102 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "remote-data": { + "cli": { + "cache": { + "enabled": false + } + }, + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:application", + "options": { + "outputPath": "dist/remote-data", + "index": "src/index.html", + "browser": "src/main.ts", + "polyfills": [], + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": ["src/favicon.ico", "src/assets"], + "styles": ["src/styles.scss"], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [], + "outputHashing": "all", + "outputMode": "server", + "server": "src/main.server.ts", + "ssr": { + "entry": "src/server.ts" + } + }, + "no-ssr": { + "budgets": [], + "outputHashing": "all", + "prerender": false, + "ssr": false + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true, + "outputMode": "server", + "server": "src/main.server.ts", + "ssr": { + "entry": "src/server.ts" + } + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "buildTarget": "remote-data:build:production" + }, + "no-ssr": { + "buildTarget": "remote-data:build:no-ssr" + }, + "development": { + "buildTarget": "remote-data:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "buildTarget": "remote-data:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "polyfills": [], + "tsConfig": "tsconfig.spec.json", + "inlineStyleLanguage": "scss", + "assets": ["src/favicon.ico", "src/assets"], + "styles": ["src/styles.scss"], + "scripts": [] + } + } + } + } + }, + "cli": { + "analytics": false + } +} diff --git a/examples/angular/remote-data/package.json b/examples/angular/remote-data/package.json new file mode 100644 index 0000000000..62f79a4980 --- /dev/null +++ b/examples/angular/remote-data/package.json @@ -0,0 +1,38 @@ +{ + "name": "tanstack-angular-table-example-remote-data", + "scripts": { + "ng": "ng", + "start": "ng serve --port 5173", + "dev": "ng serve --port 5173", + "start:no-ssr": "ng serve --configuration=no-ssr", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "test": "ng test", + "lint": "eslint ./src", + "serve:ssr:remote-data": "node dist/remote-data/server/server.mjs" + }, + "private": true, + "dependencies": { + "@angular/common": "^21.2.12", + "@angular/compiler": "^21.2.12", + "@angular/core": "^21.2.12", + "@angular/forms": "^21.2.12", + "@angular/platform-browser": "^21.2.12", + "@angular/platform-browser-dynamic": "^21.2.12", + "@angular/platform-server": "^21.2.12", + "@angular/router": "^21.2.12", + "@angular/ssr": "^21.2.10", + "@tanstack/angular-table": "^9.0.0-alpha.45", + "express": "^5.2.1", + "rxjs": "~7.8.2" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^21.2.10", + "@angular/cli": "^21.2.10", + "@angular/compiler-cli": "^21.2.12", + "@types/express": "^5.0.6", + "@types/node": "^25.6.2", + "tslib": "^2.8.1", + "typescript": "6.0.3" + } +} diff --git a/examples/angular/remote-data/src/app/app.config.server.ts b/examples/angular/remote-data/src/app/app.config.server.ts new file mode 100644 index 0000000000..0b2c82509c --- /dev/null +++ b/examples/angular/remote-data/src/app/app.config.server.ts @@ -0,0 +1,11 @@ +import { mergeApplicationConfig } from '@angular/core' +import { provideServerRendering, withRoutes } from '@angular/ssr' +import { appConfig } from './app.config' +import { serverRoutes } from './app.routes.server' +import type { ApplicationConfig } from '@angular/core' + +const serverConfig: ApplicationConfig = { + providers: [provideServerRendering(withRoutes(serverRoutes))], +} + +export const config = mergeApplicationConfig(appConfig, serverConfig) diff --git a/examples/angular/remote-data/src/app/app.config.ts b/examples/angular/remote-data/src/app/app.config.ts new file mode 100644 index 0000000000..39627a5e8e --- /dev/null +++ b/examples/angular/remote-data/src/app/app.config.ts @@ -0,0 +1,19 @@ +import { + provideClientHydration, + withEventReplay, + withHttpTransferCacheOptions, +} from '@angular/platform-browser' +import { provideBrowserGlobalErrorListeners } from '@angular/core' +import type { ApplicationConfig } from '@angular/core' + +export const appConfig: ApplicationConfig = { + providers: [ + provideBrowserGlobalErrorListeners(), + provideClientHydration( + withEventReplay(), + withHttpTransferCacheOptions({ + includeHeaders: ['X-Total-Count'], + }), + ), + ], +} diff --git a/examples/angular/remote-data/src/app/app.html b/examples/angular/remote-data/src/app/app.html new file mode 100644 index 0000000000..632a612a44 --- /dev/null +++ b/examples/angular/remote-data/src/app/app.html @@ -0,0 +1,120 @@ +
+
+ +
+ +
+ + + @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { + + @for (header of headerGroup.headers; track header.id) { + + } + + } + + + @for (row of table.getRowModel().rows; track row.id) { + + + @for (cell of row.getAllCells(); track cell.id) { + + } + + } + +
+ @if (!header.isPlaceholder) { +
+ +
+
+ + @if (header.column.getIsSorted() === 'asc') { + 🔼 + } + @if (header.column.getIsSorted() === 'desc') { + 🔽 + } +
+ } +
+ +
+
+
+
+ + @if (data.isLoading()) { + Loading... + } +
+ +
+
+ + + + + +
Page
+ + {{ (table.store.state.pagination.pageIndex + 1).toLocaleString() }} of + {{ table.getPageCount().toLocaleString() }} + +
+ + | Go to page: + + + + +
diff --git a/examples/angular/remote-data/src/app/app.routes.server.ts b/examples/angular/remote-data/src/app/app.routes.server.ts new file mode 100644 index 0000000000..09fcfa3cc9 --- /dev/null +++ b/examples/angular/remote-data/src/app/app.routes.server.ts @@ -0,0 +1,9 @@ +import { RenderMode } from '@angular/ssr' +import type { ServerRoute } from '@angular/ssr' + +export const serverRoutes: Array = [ + { + path: '**', + renderMode: RenderMode.Server, + }, +] diff --git a/examples/angular/remote-data/src/app/app.ts b/examples/angular/remote-data/src/app/app.ts new file mode 100644 index 0000000000..dd15a8c0c1 --- /dev/null +++ b/examples/angular/remote-data/src/app/app.ts @@ -0,0 +1,182 @@ +import { HttpClient, HttpParams } from '@angular/common/http' +import { + ChangeDetectionStrategy, + Component, + inject, + linkedSignal, + signal, +} from '@angular/core' +import { ReactiveFormsModule } from '@angular/forms' +import { + FlexRender, + createColumnHelper, + globalFilteringFeature, + injectTable, + rowPaginationFeature, + rowSortingFeature, + tableFeatures, +} from '@tanstack/angular-table' +import { map } from 'rxjs' +import { rxResource } from '@angular/core/rxjs-interop' +import type { + ColumnDef, + PaginationState, + SortingState, +} from '@tanstack/angular-table' +import type { WritableSignal } from '@angular/core' + +export type Todo = { + userId: number + id: number + title: string + completed: boolean +} + +const _features = tableFeatures({ + rowPaginationFeature, + globalFilteringFeature, + rowSortingFeature, +}) + +const columnHelper = createColumnHelper() + +type TodoResponse = { items: Array; totalCount: number } + +@Component({ + selector: 'app-root', + imports: [FlexRender, ReactiveFormsModule], + templateUrl: './app.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class App { + readonly client = inject(HttpClient) + readonly pagination = signal({ + pageSize: 10, + pageIndex: 0, + }) + + readonly sorting = signal([{ id: 'id', desc: false }]) + readonly globalFilter = signal(null) + readonly data = rxResource({ + params: () => ({ + page: this.pagination(), + globalFilter: this.globalFilter(), + sorting: this.sorting(), + }), + stream: ({ params: { page, globalFilter, sorting } }) => { + let httpParams = new HttpParams({ + fromObject: { + _page: page.pageIndex + 1, + _limit: page.pageSize, + }, + }) + if (globalFilter) { + httpParams = httpParams.set('title_like', globalFilter) + } + if (sorting.length) { + const keys: Array = [] + const orders: Array = [] + for (const sort of sorting) { + keys.push(sort.id) + orders.push(sort.desc ? 'desc' : 'asc') + } + httpParams = httpParams + .set('_sort', keys.join(',')) + .set('_order', orders.join(',')) + } + + return this.client + .get< + Array + >(`https://jsonplaceholder.typicode.com/todos`, { params: httpParams, observe: 'response' }) + .pipe( + map((response) => { + return { + items: response.body ?? [], + totalCount: Number(response.headers.get('X-Total-Count')), + } satisfies TodoResponse + }), + ) + }, + }) + + readonly columns = [ + columnHelper.accessor('id', { + id: 'id', + cell: (info) => info.getValue(), + header: () => 'Id', + footer: (props) => props.column.id, + }), + columnHelper.accessor('title', { + id: 'title', + cell: (info) => info.getValue(), + header: () => 'Title', + footer: (props) => props.column.id, + }), + columnHelper.accessor('completed', { + id: 'completed', + cell: (info) => (info.getValue() ? `✅` : `❌`), + header: () => 'Completed', + footer: (props) => props.column.id, + }), + ] as Array> + + // Keep previous value + readonly dataWithLatest: WritableSignal = linkedSignal({ + source: () => ({ + value: this.data.value(), + status: this.data.status(), + }), + computation: (source, previous) => { + if (previous && source.status === 'loading') return previous.value + return source.value ?? { items: [], totalCount: 0 } + }, + }) + + readonly table = injectTable(() => { + const data = this.dataWithLatest() + return { + debugTable: true, + _features, + data: data.items, + columns: this.columns, + state: { + pagination: this.pagination(), + globalFilter: this.globalFilter(), + sorting: this.sorting(), + }, + manualFiltering: true, + manualPagination: true, + manualSorting: true, + rowCount: data.totalCount, + onPaginationChange: (updater) => + typeof updater === 'function' + ? this.pagination.update(updater) + : this.pagination.set(updater), + onSortingChange: (updater) => { + typeof updater === 'function' + ? this.sorting.update(updater) + : this.sorting.set(updater) + }, + onGlobalFilterChange: (updater) => { + typeof updater === 'function' + ? this.globalFilter.update(updater) + : this.globalFilter.set(updater) + this.pagination.update((page) => ({ + ...page, + pageIndex: 0, + })) + }, + } + }) + + onPageInputChange(event: Event): void { + const inputElement = event.target as HTMLInputElement + const page = inputElement.value ? Number(inputElement.value) - 1 : 0 + this.table.setPageIndex(page) + } + + onPageSizeChange(event: any): void { + this.table.setPageSize(Number(event.target.value)) + } +} diff --git a/examples/angular/remote-data/src/favicon.ico b/examples/angular/remote-data/src/favicon.ico new file mode 100644 index 0000000000..57614f9c96 Binary files /dev/null and b/examples/angular/remote-data/src/favicon.ico differ diff --git a/examples/angular/remote-data/src/index.html b/examples/angular/remote-data/src/index.html new file mode 100644 index 0000000000..61c94c972f --- /dev/null +++ b/examples/angular/remote-data/src/index.html @@ -0,0 +1,13 @@ + + + + + Expanding + + + + + + + + diff --git a/examples/angular/remote-data/src/main.server.ts b/examples/angular/remote-data/src/main.server.ts new file mode 100644 index 0000000000..bd72130039 --- /dev/null +++ b/examples/angular/remote-data/src/main.server.ts @@ -0,0 +1,9 @@ +import { bootstrapApplication } from '@angular/platform-browser' +import { config } from './app/app.config.server' +import { App } from './app/app' +import type { BootstrapContext } from '@angular/platform-browser' + +const bootstrap = (context: BootstrapContext) => + bootstrapApplication(App, config, context) + +export default bootstrap diff --git a/examples/angular/remote-data/src/main.ts b/examples/angular/remote-data/src/main.ts new file mode 100644 index 0000000000..8192dca694 --- /dev/null +++ b/examples/angular/remote-data/src/main.ts @@ -0,0 +1,5 @@ +import { bootstrapApplication } from '@angular/platform-browser' +import { appConfig } from './app/app.config' +import { App } from './app/app' + +bootstrapApplication(App, appConfig).catch((err) => console.error(err)) diff --git a/examples/angular/remote-data/src/server.ts b/examples/angular/remote-data/src/server.ts new file mode 100644 index 0000000000..62db1a15fd --- /dev/null +++ b/examples/angular/remote-data/src/server.ts @@ -0,0 +1,68 @@ +import { join } from 'node:path' +import { + AngularNodeAppEngine, + createNodeRequestHandler, + isMainModule, + writeResponseToNodeResponse, +} from '@angular/ssr/node' +import express from 'express' + +const browserDistFolder = join(import.meta.dirname, '../browser') + +const app = express() +const angularApp = new AngularNodeAppEngine() + +/** + * Example Express Rest API endpoints can be defined here. + * Uncomment and define endpoints as necessary. + * + * Example: + * ```ts + * app.get('/api/{*splat}', (req, res) => { + * // Handle API request + * }); + * ``` + */ + +/** + * Serve static files from /browser + */ +app.use( + express.static(browserDistFolder, { + maxAge: '1y', + index: false, + redirect: false, + }), +) + +/** + * Handle all other requests by rendering the Angular application. + */ +app.use((req, res, next) => { + angularApp + .handle(req) + .then((response) => + response ? writeResponseToNodeResponse(response, res) : next(), + ) + .catch(next) +}) + +/** + * Start the server if this module is the main entry point, or it is ran via PM2. + * The server listens on the port defined by the `PORT` environment variable, or defaults to 4000. + */ +if (isMainModule(import.meta.url) || process.env['pm_id']) { + const port = process.env['PORT'] || 4000 + app.listen(port, (error) => { + if (error) { + throw error + } + + console.log(`Node Express server listening on http://localhost:${port}`) + }) +} + +/** + * Request handler used by the Angular CLI (for dev-server and during build) or Firebase Cloud Functions. + */ +export const reqHandler = createNodeRequestHandler(app) diff --git a/examples/angular/remote-data/src/styles.scss b/examples/angular/remote-data/src/styles.scss new file mode 100644 index 0000000000..c3392b8566 --- /dev/null +++ b/examples/angular/remote-data/src/styles.scss @@ -0,0 +1,371 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tbody td { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/angular/remote-data/tsconfig.app.json b/examples/angular/remote-data/tsconfig.app.json new file mode 100644 index 0000000000..c5420c6212 --- /dev/null +++ b/examples/angular/remote-data/tsconfig.app.json @@ -0,0 +1,10 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": ["node"] + }, + "files": ["src/main.ts", "src/main.server.ts", "src/server.ts"], + "include": ["src/**/*.d.ts"] +} diff --git a/examples/angular/remote-data/tsconfig.json b/examples/angular/remote-data/tsconfig.json new file mode 100644 index 0000000000..016cf916ae --- /dev/null +++ b/examples/angular/remote-data/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "outDir": "./dist/out-tsc", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "esModuleInterop": true, + "sourceMap": true, + "declaration": false, + "experimentalDecorators": true, + "moduleResolution": "bundler", + "importHelpers": true, + "target": "ES2022", + "module": "ES2022", + "useDefineForClassFields": false, + "lib": ["ES2022", "dom"] + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + }, + "include": ["**/*.ts", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/angular/row-dnd/.gitignore b/examples/angular/row-dnd/.gitignore new file mode 100644 index 0000000000..854acd5fc0 --- /dev/null +++ b/examples/angular/row-dnd/.gitignore @@ -0,0 +1,44 @@ +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out + +# Node +/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/mcp.json +.history/* + +# Miscellaneous +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings +__screenshots__/ + +# System files +.DS_Store +Thumbs.db diff --git a/examples/angular/row-dnd/README.md b/examples/angular/row-dnd/README.md new file mode 100644 index 0000000000..12c5edf377 --- /dev/null +++ b/examples/angular/row-dnd/README.md @@ -0,0 +1,59 @@ +# Row Drag and Drop + +This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.1.0. + +## Development server + +To start a local development server, run: + +```bash +ng serve +``` + +Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files. + +## Code scaffolding + +Angular CLI includes powerful code scaffolding tools. To generate a new component, run: + +```bash +ng generate component component-name +``` + +For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run: + +```bash +ng generate --help +``` + +## Building + +To build the project run: + +```bash +ng build +``` + +This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed. + +## Running unit tests + +To execute unit tests with the [Vitest](https://vitest.dev/) test runner, use the following command: + +```bash +ng test +``` + +## Running end-to-end tests + +For end-to-end (e2e) testing, run: + +```bash +ng e2e +``` + +Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs. + +## Additional Resources + +For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. diff --git a/examples/angular/row-dnd/angular.json b/examples/angular/row-dnd/angular.json new file mode 100644 index 0000000000..346ef51c96 --- /dev/null +++ b/examples/angular/row-dnd/angular.json @@ -0,0 +1,99 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "cli": { + "packageManager": "pnpm", + "analytics": false, + "cache": { + "enabled": false + } + }, + "newProjectRoot": "projects", + "projects": { + "row-dnd": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "inlineTemplate": true, + "inlineStyle": true, + "skipTests": true + }, + "@schematics/angular:class": { + "skipTests": true + }, + "@schematics/angular:directive": { + "skipTests": true + }, + "@schematics/angular:guard": { + "skipTests": true + }, + "@schematics/angular:interceptor": { + "skipTests": true + }, + "@schematics/angular:pipe": { + "skipTests": true + }, + "@schematics/angular:resolver": { + "skipTests": true + }, + "@schematics/angular:service": { + "skipTests": true + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular/build:application", + "options": { + "browser": "src/main.ts", + "tsConfig": "tsconfig.app.json", + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": ["src/styles.css"] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kB", + "maximumError": "8kB" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular/build:dev-server", + "configurations": { + "production": { + "buildTarget": "row-dnd:build:production" + }, + "development": { + "buildTarget": "row-dnd:build:development" + } + }, + "defaultConfiguration": "development" + } + } + } + } +} diff --git a/examples/angular/row-dnd/package.json b/examples/angular/row-dnd/package.json new file mode 100644 index 0000000000..9a2ca23153 --- /dev/null +++ b/examples/angular/row-dnd/package.json @@ -0,0 +1,32 @@ +{ + "name": "tanstack-angular-table-example-row-dnd", + "scripts": { + "ng": "ng", + "start": "ng serve --port 5173", + "dev": "ng serve --port 5173", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "lint": "eslint ./src" + }, + "private": true, + "packageManager": "pnpm@11.0.9", + "dependencies": { + "@angular/cdk": "^21.2.10", + "@angular/common": "^21.2.12", + "@angular/compiler": "^21.2.12", + "@angular/core": "^21.2.12", + "@angular/forms": "^21.2.12", + "@angular/platform-browser": "^21.2.12", + "@angular/router": "^21.2.12", + "@faker-js/faker": "^10.4.0", + "@tanstack/angular-table": "^9.0.0-alpha.45", + "rxjs": "~7.8.2", + "tslib": "^2.8.1" + }, + "devDependencies": { + "@angular/build": "^21.2.10", + "@angular/cli": "^21.2.10", + "@angular/compiler-cli": "^21.2.12", + "typescript": "6.0.3" + } +} diff --git a/examples/angular/row-dnd/public/favicon.ico b/examples/angular/row-dnd/public/favicon.ico new file mode 100644 index 0000000000..57614f9c96 Binary files /dev/null and b/examples/angular/row-dnd/public/favicon.ico differ diff --git a/examples/angular/row-dnd/src/app/app.config.ts b/examples/angular/row-dnd/src/app/app.config.ts new file mode 100644 index 0000000000..cbb47d366c --- /dev/null +++ b/examples/angular/row-dnd/src/app/app.config.ts @@ -0,0 +1,6 @@ +import { provideBrowserGlobalErrorListeners } from '@angular/core' +import type { ApplicationConfig } from '@angular/core' + +export const appConfig: ApplicationConfig = { + providers: [provideBrowserGlobalErrorListeners()], +} diff --git a/examples/angular/row-dnd/src/app/app.css b/examples/angular/row-dnd/src/app/app.css new file mode 100644 index 0000000000..e2af5a36c4 --- /dev/null +++ b/examples/angular/row-dnd/src/app/app.css @@ -0,0 +1,360 @@ +.cdk-drag-preview { + box-sizing: border-box; + box-shadow: + 0 5px 5px -3px rgba(0, 0, 0, 0.2), + 0 8px 10px 1px rgba(0, 0, 0, 0.14), + 0 3px 14px 2px rgba(0, 0, 0, 0.12); +} + +.cdk-drag-placeholder { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} + +.cdk-drag-placeholder > td { + background: #ccc; +} + +.cdk-drag-animating { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} + +.cdk-drop-list-dragging tr[cdkdrag]:not(.cdk-drag-placeholder) { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/angular/row-dnd/src/app/app.html b/examples/angular/row-dnd/src/app/app.html new file mode 100644 index 0000000000..4395fe43cb --- /dev/null +++ b/examples/angular/row-dnd/src/app/app.html @@ -0,0 +1,44 @@ +
+ + +
+ + + @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { + + @for (header of headerGroup.headers; track header.id) { + @if (!header.isPlaceholder) { + + } + } + + } + + + @for (row of table.getRowModel().rows; track row.id) { + + @for (cell of row.getVisibleCells(); track cell.id) { + + } + + } + +
+ +
+
+
+ +
+
+
+ +
{{ sortedIds() | json }}
+
diff --git a/examples/angular/row-dnd/src/app/app.ts b/examples/angular/row-dnd/src/app/app.ts new file mode 100644 index 0000000000..f5361a670d --- /dev/null +++ b/examples/angular/row-dnd/src/app/app.ts @@ -0,0 +1,97 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + signal, +} from '@angular/core' +import { + FlexRender, + columnSizingFeature, + columnVisibilityFeature, + flexRenderComponent, + injectTable, + rowPaginationFeature, + tableFeatures, +} from '@tanstack/angular-table' +import { CdkDrag, CdkDropList, moveItemInArray } from '@angular/cdk/drag-drop' +import { JsonPipe } from '@angular/common' +import { DragHandleCell } from './drag-handle-cell/drag-handle-cell' +import { makeData } from './makeData' +import type { Person } from './makeData' +import type { CdkDragDrop } from '@angular/cdk/drag-drop' +import type { ColumnDef } from '@tanstack/angular-table' + +const _tableFeatures = tableFeatures({ + rowPaginationFeature, + columnSizingFeature, + columnVisibilityFeature, +}) + +const defaultColumns: Array> = [ + { + id: 'drag-handle', + header: 'Move', + cell: () => flexRenderComponent(DragHandleCell), + size: 60, + }, + { + accessorKey: 'firstName', + cell: (info) => info.getValue(), + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => info.getValue(), + header: () => `Last Name`, + }, + { + accessorKey: 'age', + header: () => 'Age', + }, + { + accessorKey: 'visits', + header: () => `Visits`, + }, + { + accessorKey: 'status', + header: 'Status', + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + }, +] + +@Component({ + selector: 'app-root', + imports: [FlexRender, CdkDropList, CdkDrag, JsonPipe], + templateUrl: './app.html', + styleUrl: './app.css', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class App { + readonly data = signal>(makeData(20)) + + readonly table = injectTable(() => { + return { + _features: _tableFeatures, + data: this.data(), + columns: defaultColumns, + getRowId: (row) => row.userId, + debugTable: true, + debugHeaders: true, + debugColumns: true, + } + }) + + readonly sortedIds = computed(() => this.data().map((data) => data.userId)) + + drop(event: CdkDragDrop>) { + const data = [...this.data()] + moveItemInArray(data, event.previousIndex, event.currentIndex) + this.data.set(data) + } + + refreshData = () => this.data.set(makeData(20)) + stressTest = () => this.data.set(makeData(1_000)) +} diff --git a/examples/angular/row-dnd/src/app/drag-handle-cell/drag-handle-cell.ts b/examples/angular/row-dnd/src/app/drag-handle-cell/drag-handle-cell.ts new file mode 100644 index 0000000000..3ce976bcad --- /dev/null +++ b/examples/angular/row-dnd/src/app/drag-handle-cell/drag-handle-cell.ts @@ -0,0 +1,10 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core' +import { CdkDragHandle } from '@angular/cdk/drag-drop' + +@Component({ + selector: 'drag-handle-cell', + template: ` `, + imports: [CdkDragHandle], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class DragHandleCell {} diff --git a/examples/angular/row-dnd/src/app/makeData.ts b/examples/angular/row-dnd/src/app/makeData.ts new file mode 100644 index 0000000000..e2103d2b87 --- /dev/null +++ b/examples/angular/row-dnd/src/app/makeData.ts @@ -0,0 +1,50 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + userId: string + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + userId: faker.string.uuid(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/angular/row-dnd/src/index.html b/examples/angular/row-dnd/src/index.html new file mode 100644 index 0000000000..11aa6a2b9d --- /dev/null +++ b/examples/angular/row-dnd/src/index.html @@ -0,0 +1,13 @@ + + + + + Row Drag and drop + + + + + + + + diff --git a/examples/angular/row-dnd/src/main.ts b/examples/angular/row-dnd/src/main.ts new file mode 100644 index 0000000000..8192dca694 --- /dev/null +++ b/examples/angular/row-dnd/src/main.ts @@ -0,0 +1,5 @@ +import { bootstrapApplication } from '@angular/platform-browser' +import { appConfig } from './app/app.config' +import { App } from './app/app' + +bootstrapApplication(App, appConfig).catch((err) => console.error(err)) diff --git a/examples/angular/row-dnd/src/styles.css b/examples/angular/row-dnd/src/styles.css new file mode 100644 index 0000000000..80b931db1e --- /dev/null +++ b/examples/angular/row-dnd/src/styles.css @@ -0,0 +1,380 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td { + border-right: 1px solid lightgray; + padding: 4px 4px; + background-color: white; +} + +td button { + padding: 1px 1rem; + cursor: grab; +} + +td:last-child { + border-right: 0; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/angular/row-dnd/tsconfig.app.json b/examples/angular/row-dnd/tsconfig.app.json new file mode 100644 index 0000000000..a0dcc37c60 --- /dev/null +++ b/examples/angular/row-dnd/tsconfig.app.json @@ -0,0 +1,11 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] +} diff --git a/examples/angular/row-dnd/tsconfig.json b/examples/angular/row-dnd/tsconfig.json new file mode 100644 index 0000000000..32789cdc2b --- /dev/null +++ b/examples/angular/row-dnd/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "isolatedModules": true, + "experimentalDecorators": true, + "importHelpers": true, + "target": "ES2022", + "module": "preserve" + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + }, + "include": ["**/*.ts", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/angular/row-selection-signal/angular.json b/examples/angular/row-selection-signal/angular.json index 7bae30f0ba..426e8915dc 100644 --- a/examples/angular/row-selection-signal/angular.json +++ b/examples/angular/row-selection-signal/angular.json @@ -3,11 +3,40 @@ "version": 1, "newProjectRoot": "projects", "projects": { - "selection": { + "row-selection-signal": { + "cli": { + "cache": { + "enabled": false + } + }, "projectType": "application", "schematics": { "@schematics/angular:component": { + "inlineTemplate": true, + "inlineStyle": true, + "skipTests": true, "style": "scss" + }, + "@schematics/angular:class": { + "skipTests": true + }, + "@schematics/angular:directive": { + "skipTests": true + }, + "@schematics/angular:guard": { + "skipTests": true + }, + "@schematics/angular:interceptor": { + "skipTests": true + }, + "@schematics/angular:pipe": { + "skipTests": true + }, + "@schematics/angular:resolver": { + "skipTests": true + }, + "@schematics/angular:service": { + "skipTests": true } }, "root": "", @@ -15,12 +44,12 @@ "prefix": "app", "architect": { "build": { - "builder": "@angular-devkit/build-angular:application", + "builder": "@angular/build:application", "options": { - "outputPath": "dist/selection-signal", + "outputPath": "dist/row-selection-signal", "index": "src/index.html", "browser": "src/main.ts", - "polyfills": ["zone.js"], + "polyfills": [], "tsConfig": "tsconfig.app.json", "inlineStyleLanguage": "scss", "assets": ["src/favicon.ico", "src/assets"], @@ -29,7 +58,18 @@ }, "configurations": { "production": { - "budgets": [], + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], "outputHashing": "all" }, "development": { @@ -41,41 +81,27 @@ "defaultConfiguration": "production" }, "serve": { - "builder": "@angular-devkit/build-angular:dev-server", + "builder": "@angular/build:dev-server", "configurations": { "production": { - "buildTarget": "selection:build:production" + "buildTarget": "row-selection-signal:build:production" }, "development": { - "buildTarget": "selection:build:development" + "buildTarget": "row-selection-signal:build:development" } }, "defaultConfiguration": "development" }, "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", + "builder": "@angular/build:extract-i18n", "options": { - "buildTarget": "selection:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "polyfills": ["zone.js", "zone.js/testing"], - "tsConfig": "tsconfig.spec.json", - "inlineStyleLanguage": "scss", - "assets": ["src/favicon.ico", "src/assets"], - "styles": ["src/styles.scss"], - "scripts": [] + "buildTarget": "row-selection-signal:build" } } } } }, "cli": { - "analytics": false, - "cache": { - "enabled": false - } + "analytics": false } } diff --git a/examples/angular/row-selection-signal/package.json b/examples/angular/row-selection-signal/package.json index 376d351e36..a78d21d923 100644 --- a/examples/angular/row-selection-signal/package.json +++ b/examples/angular/row-selection-signal/package.json @@ -1,39 +1,32 @@ { - "name": "tanstack-table-example-angular-row-selection-signal", - "version": "0.0.0", + "name": "tanstack-angular-table-example-row-selection-signal", "scripts": { "ng": "ng", - "start": "ng serve", + "start": "ng serve --port 5173", + "dev": "ng serve --port 5173", "build": "ng build", "watch": "ng build --watch --configuration development", - "test": "ng test" + "test": "ng test", + "lint": "eslint ./src" }, "private": true, "dependencies": { - "@angular/animations": "^17.3.9", - "@angular/common": "^17.3.9", - "@angular/compiler": "^17.3.9", - "@angular/core": "^17.3.9", - "@angular/forms": "^17.3.9", - "@angular/platform-browser": "^17.3.9", - "@angular/platform-browser-dynamic": "^17.3.9", - "@faker-js/faker": "^8.4.1", - "@tanstack/angular-table": "^8.20.5", - "rxjs": "~7.8.1", - "tslib": "^2.6.2", - "zone.js": "~0.14.4" + "@angular/animations": "^21.2.12", + "@angular/common": "^21.2.12", + "@angular/compiler": "^21.2.12", + "@angular/core": "^21.2.12", + "@angular/forms": "^21.2.12", + "@angular/platform-browser": "^21.2.12", + "@angular/platform-browser-dynamic": "^21.2.12", + "@faker-js/faker": "^10.4.0", + "@tanstack/angular-table": "^9.0.0-alpha.45", + "rxjs": "~7.8.2", + "tslib": "^2.8.1" }, "devDependencies": { - "@angular-devkit/build-angular": "^17.3.8", - "@angular/cli": "^17.3.8", - "@angular/compiler-cli": "^17.3.9", - "@types/jasmine": "~5.1.4", - "jasmine-core": "~5.1.2", - "karma": "~6.4.3", - "karma-chrome-launcher": "~3.2.0", - "karma-coverage": "~2.2.1", - "karma-jasmine": "~5.1.0", - "karma-jasmine-html-reporter": "~2.1.0", - "typescript": "5.4.5" + "@angular/build": "^21.2.10", + "@angular/cli": "^21.2.10", + "@angular/compiler-cli": "^21.2.12", + "typescript": "6.0.3" } } diff --git a/examples/angular/row-selection-signal/src/app/app.component.html b/examples/angular/row-selection-signal/src/app/app.component.html index c2dfb725f7..3a59b083f6 100644 --- a/examples/angular/row-selection-signal/src/app/app.component.html +++ b/examples/angular/row-selection-signal/src/app/app.component.html @@ -1,5 +1,9 @@ -
-
+
+ + +
@@ -20,10 +24,8 @@ @if (header.column.getCanFilter()) {
- + +
} } @@ -38,11 +40,7 @@ @for (cell of row.getVisibleCells(); track cell.id) { -
{{ renderCell }} @@ -53,7 +51,7 @@
+ - Page Rows ({{ table.getRowModel().rows.length }}) + Page Rows ({{ table.getRowModel().rows.length.toLocaleString() }})
-
-
+
+
- +
Page
- {{ table.getState().pagination.pageIndex + 1 }} of - {{ table.getPageCount() }} + {{ (table.store.state.pagination.pageIndex + 1).toLocaleString() }} of + {{ table.getPageCount().toLocaleString() }}
- + | Go to page: - @for (pageSize of [10, 20, 30, 40, 50]; track pageSize) { } @@ -126,18 +121,13 @@

- {{ rowSelectionLength() }} of - {{ table.getPreFilteredRowModel().rows.length }} Total Rows + {{ rowSelectionLength().toLocaleString() }} of + {{ table.getPreFilteredRowModel().rows.length.toLocaleString() }} Total Rows


- -
-
-
diff --git a/examples/angular/row-selection-signal/src/app/app.component.ts b/examples/angular/row-selection-signal/src/app/app.component.ts index 5e2e103b4a..55152adb6b 100644 --- a/examples/angular/row-selection-signal/src/app/app.component.ts +++ b/examples/angular/row-selection-signal/src/app/app.component.ts @@ -3,28 +3,39 @@ import { Component, computed, signal, - TemplateRef, viewChild, } from '@angular/core' import { - ColumnDef, - createAngularTable, FlexRenderDirective, - getCoreRowModel, - getFilteredRowModel, - getPaginationRowModel, - RowSelectionState, + columnFilteringFeature, + columnVisibilityFeature, + createFilteredRowModel, + createPaginatedRowModel, + filterFns, + injectTable, + rowPaginationFeature, + rowSelectionFeature, + tableFeatures, } from '@tanstack/angular-table' import { FilterComponent } from './filter' -import { makeData, type Person } from './makeData' +import { makeData } from './makeData' import { TableHeadSelectionComponent, TableRowSelectionComponent, } from './selection-column.component' +import type { Person } from './makeData' +import type { ColumnDef, RowSelectionState } from '@tanstack/angular-table' +import type { TemplateRef } from '@angular/core' + +const _features = tableFeatures({ + columnFilteringFeature, + columnVisibilityFeature, + rowPaginationFeature, + rowSelectionFeature, +}) @Component({ selector: 'app-root', - standalone: true, imports: [FilterComponent, FlexRenderDirective], templateUrl: './app.component.html', changeDetection: ChangeDetectionStrategy.OnPush, @@ -32,12 +43,12 @@ import { export class AppComponent { private readonly rowSelection = signal({}) readonly globalFilter = signal('') - readonly data = signal(makeData(10_000)) + readonly data = signal(makeData(1_000)) readonly ageHeaderCell = viewChild.required>('ageHeaderCell') - readonly columns: ColumnDef[] = [ + readonly columns: Array> = [ { id: 'select', header: () => TableHeadSelectionComponent, @@ -45,31 +56,31 @@ export class AppComponent { }, { header: 'Name', - footer: props => props.column.id, + footer: (props) => props.column.id, columns: [ { accessorKey: 'firstName', - cell: info => info.getValue(), - footer: props => props.column.id, + cell: (info) => info.getValue(), + footer: (props) => props.column.id, header: 'First name', }, { - accessorFn: row => row.lastName, + accessorFn: (row) => row.lastName, id: 'lastName', - cell: info => info.getValue(), + cell: (info) => info.getValue(), header: () => 'Last Name', - footer: props => props.column.id, + footer: (props) => props.column.id, }, ], }, { header: 'Info', - footer: props => props.column.id, + footer: (props) => props.column.id, columns: [ { accessorKey: 'age', header: () => this.ageHeaderCell(), - footer: props => props.column.id, + footer: (props) => props.column.id, }, { header: 'More Info', @@ -77,17 +88,17 @@ export class AppComponent { { accessorKey: 'visits', header: () => 'Visits', - footer: props => props.column.id, + footer: (props) => props.column.id, }, { accessorKey: 'status', header: 'Status', - footer: props => props.column.id, + footer: (props) => props.column.id, }, { accessorKey: 'progress', header: 'Profile Progress', - footer: props => props.column.id, + footer: (props) => props.column.id, }, ], }, @@ -95,33 +106,36 @@ export class AppComponent { }, ] - table = createAngularTable(() => ({ - data: this.data(), + // TODO make this generic infer without passing in manually + table = injectTable(() => ({ + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + }, columns: this.columns, + data: this.data(), state: { rowSelection: this.rowSelection(), }, enableRowSelection: true, // enable row selection for all rows // enableRowSelection: row => row.original.age > 18, // or enable row selection conditionally per row - onRowSelectionChange: updaterOrValue => { + onRowSelectionChange: (updaterOrValue) => { this.rowSelection.set( typeof updaterOrValue === 'function' ? updaterOrValue(this.rowSelection()) - : updaterOrValue + : updaterOrValue, ) }, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getPaginationRowModel: getPaginationRowModel(), debugTable: true, })) readonly stringifiedRowSelection = computed(() => - JSON.stringify(this.rowSelection(), null, 2) + JSON.stringify(this.rowSelection(), null, 2), ) readonly rowSelectionLength = computed( - () => Object.keys(this.rowSelection()).length + () => Object.keys(this.rowSelection()).length, ) onPageInputChange(event: Event): void { @@ -137,11 +151,10 @@ export class AppComponent { logSelectedFlatRows(): void { console.info( 'table.getSelectedRowModel().flatRows', - this.table.getSelectedRowModel().flatRows + this.table.getSelectedRowModel().flatRows, ) } - refreshData(): void { - this.data.set(makeData(10_000)) - } + refreshData = () => this.data.set(makeData(1_000)) + stressTest = () => this.data.set(makeData(200_000)) } diff --git a/examples/angular/row-selection-signal/src/app/app.config.ts b/examples/angular/row-selection-signal/src/app/app.config.ts index f27099f33c..f997e614ac 100644 --- a/examples/angular/row-selection-signal/src/app/app.config.ts +++ b/examples/angular/row-selection-signal/src/app/app.config.ts @@ -1,4 +1,4 @@ -import { ApplicationConfig } from '@angular/core' +import type { ApplicationConfig } from '@angular/core' export const appConfig: ApplicationConfig = { providers: [], diff --git a/examples/angular/row-selection-signal/src/app/filter.ts b/examples/angular/row-selection-signal/src/app/filter.ts index cf0b567b36..3a46e301bf 100644 --- a/examples/angular/row-selection-signal/src/app/filter.ts +++ b/examples/angular/row-selection-signal/src/app/filter.ts @@ -1,17 +1,16 @@ -import { CommonModule } from '@angular/common' -import { Component, input, OnInit } from '@angular/core' -import { Column } from '@tanstack/angular-table' -import type { Table } from '@tanstack/angular-table' +import { Component, input } from '@angular/core' +import type { OnInit } from '@angular/core' +import type { Column, RowData, Table } from '@tanstack/angular-table' @Component({ selector: 'app-table-filter', template: ` @if (columnType) { @if (columnType == 'number') { -
+
} }`, - standalone: true, - imports: [CommonModule], }) -export class FilterComponent implements OnInit { +export class FilterComponent implements OnInit { column = input.required>() - table = input.required>() + table = input.required>() columnType!: string @@ -53,13 +50,13 @@ export class FilterComponent implements OnInit { } getMinValue() { - const minValue = this.column().getFilterValue() as any + const minValue = this.column().getFilterValue() return (minValue?.[0] ?? '') as string } getMaxValue() { - const maxValue = this.column().getFilterValue() as any + const maxValue = this.column().getFilterValue() return (maxValue?.[1] ?? '') as string } diff --git a/examples/angular/row-selection-signal/src/app/makeData.ts b/examples/angular/row-selection-signal/src/app/makeData.ts index 331dd1eb19..b9fb014aba 100644 --- a/examples/angular/row-selection-signal/src/app/makeData.ts +++ b/examples/angular/row-selection-signal/src/app/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,13 +29,13 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] return range(len).map((d): Person => { return { ...newPerson(), diff --git a/examples/angular/row-selection-signal/src/app/selection-column.component.ts b/examples/angular/row-selection-signal/src/app/selection-column.component.ts index d6f6045bd9..40d16fb880 100644 --- a/examples/angular/row-selection-signal/src/app/selection-column.component.ts +++ b/examples/angular/row-selection-signal/src/app/selection-column.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, input } from '@angular/core' -import { Row, Table } from '@tanstack/angular-table' +import type { Row, RowData, Table } from '@tanstack/angular-table' @Component({ template: ` @@ -11,21 +11,21 @@ import { Row, Table } from '@tanstack/angular-table' /> `, host: { - class: 'px-1 block', + class: 'selection-cell', }, standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class TableHeadSelectionComponent { +export class TableHeadSelectionComponent { // Your component should also reflect the fields you use as props in flexRenderer directive. // Define the fields as input you want to use in your component // ie. In this case, you are passing HeaderContext object as props in flexRenderer directive. // You can define and use the table field, which is defined in HeaderContext. // Please take note that only signal based input is supported. - //column = input.required>() - //header = input.required>() - table = input.required>() + // column = input.required>() + // header = input.required>() + table = input.required>() } @Component({ @@ -37,11 +37,11 @@ export class TableHeadSelectionComponent { /> `, host: { - class: 'px-1 block', + class: 'selection-cell', }, standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class TableRowSelectionComponent { - row = input.required>() +export class TableRowSelectionComponent { + row = input.required>() } diff --git a/examples/angular/row-selection-signal/src/index.html b/examples/angular/row-selection-signal/src/index.html index 27917d2b28..4f40d38cef 100644 --- a/examples/angular/row-selection-signal/src/index.html +++ b/examples/angular/row-selection-signal/src/index.html @@ -6,7 +6,6 @@ - diff --git a/examples/angular/row-selection-signal/src/main.ts b/examples/angular/row-selection-signal/src/main.ts index 0c3b92057c..c3d8f9af99 100644 --- a/examples/angular/row-selection-signal/src/main.ts +++ b/examples/angular/row-selection-signal/src/main.ts @@ -2,4 +2,4 @@ import { bootstrapApplication } from '@angular/platform-browser' import { appConfig } from './app/app.config' import { AppComponent } from './app/app.component' -bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err)) +bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err)) diff --git a/examples/angular/row-selection-signal/src/styles.scss b/examples/angular/row-selection-signal/src/styles.scss index 43c09e0f6b..b9af6ded32 100644 --- a/examples/angular/row-selection-signal/src/styles.scss +++ b/examples/angular/row-selection-signal/src/styles.scss @@ -5,6 +5,8 @@ html { table { border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; } tbody { @@ -24,3 +26,340 @@ tfoot { tfoot th { font-weight: normal; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/angular/row-selection-signal/tsconfig.json b/examples/angular/row-selection-signal/tsconfig.json index fd2d87ac26..016cf916ae 100644 --- a/examples/angular/row-selection-signal/tsconfig.json +++ b/examples/angular/row-selection-signal/tsconfig.json @@ -1,4 +1,3 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ { "compileOnSave": false, "compilerOptions": { @@ -14,7 +13,7 @@ "sourceMap": true, "declaration": false, "experimentalDecorators": true, - "moduleResolution": "node", + "moduleResolution": "bundler", "importHelpers": true, "target": "ES2022", "module": "ES2022", @@ -26,5 +25,6 @@ "strictInjectionParameters": true, "strictInputAccessModifiers": true, "strictTemplates": true - } + }, + "include": ["**/*.ts", "vite.config.js", "vite.config.ts"] } diff --git a/examples/angular/row-selection/.devcontainer/devcontainer.json b/examples/angular/row-selection/.devcontainer/devcontainer.json deleted file mode 100644 index 36f47d8762..0000000000 --- a/examples/angular/row-selection/.devcontainer/devcontainer.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "Node.js", - "image": "mcr.microsoft.com/devcontainers/javascript-node:18" -} diff --git a/examples/angular/row-selection/.editorconfig b/examples/angular/row-selection/.editorconfig deleted file mode 100644 index 59d9a3a3e7..0000000000 --- a/examples/angular/row-selection/.editorconfig +++ /dev/null @@ -1,16 +0,0 @@ -# Editor configuration, see https://editorconfig.org -root = true - -[*] -charset = utf-8 -indent_style = space -indent_size = 2 -insert_final_newline = true -trim_trailing_whitespace = true - -[*.ts] -quote_type = single - -[*.md] -max_line_length = off -trim_trailing_whitespace = false diff --git a/examples/angular/row-selection/.gitignore b/examples/angular/row-selection/.gitignore index 0711527ef9..854acd5fc0 100644 --- a/examples/angular/row-selection/.gitignore +++ b/examples/angular/row-selection/.gitignore @@ -1,4 +1,4 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. # Compiled output /dist @@ -26,6 +26,7 @@ yarn-error.log !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json +!.vscode/mcp.json .history/* # Miscellaneous @@ -36,6 +37,7 @@ yarn-error.log /libpeerconnection.log testem.log /typings +__screenshots__/ # System files .DS_Store diff --git a/examples/angular/row-selection/README.md b/examples/angular/row-selection/README.md index 73a201f1eb..d8f580e1fa 100644 --- a/examples/angular/row-selection/README.md +++ b/examples/angular/row-selection/README.md @@ -1,27 +1,59 @@ -# Selection +# Row Selection -This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.1.2. +This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.1.0. ## Development server -Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. +To start a local development server, run: + +```bash +ng serve +``` + +Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files. ## Code scaffolding -Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. +Angular CLI includes powerful code scaffolding tools. To generate a new component, run: + +```bash +ng generate component component-name +``` + +For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run: + +```bash +ng generate --help +``` -## Build +## Building -Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. +To build the project run: + +```bash +ng build +``` + +This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed. ## Running unit tests -Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). +To execute unit tests with the [Vitest](https://vitest.dev/) test runner, use the following command: + +```bash +ng test +``` ## Running end-to-end tests -Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. +For end-to-end (e2e) testing, run: + +```bash +ng e2e +``` + +Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs. -## Further help +## Additional Resources -To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. +For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. diff --git a/examples/angular/row-selection/angular.json b/examples/angular/row-selection/angular.json index 6a37fa4647..c1bb1272f7 100644 --- a/examples/angular/row-selection/angular.json +++ b/examples/angular/row-selection/angular.json @@ -1,13 +1,43 @@ { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, + "cli": { + "packageManager": "pnpm", + "analytics": false, + "cache": { + "enabled": false + } + }, "newProjectRoot": "projects", "projects": { - "selection": { + "row-selection": { "projectType": "application", "schematics": { "@schematics/angular:component": { - "style": "scss" + "inlineTemplate": true, + "inlineStyle": true, + "skipTests": true + }, + "@schematics/angular:class": { + "skipTests": true + }, + "@schematics/angular:directive": { + "skipTests": true + }, + "@schematics/angular:guard": { + "skipTests": true + }, + "@schematics/angular:interceptor": { + "skipTests": true + }, + "@schematics/angular:pipe": { + "skipTests": true + }, + "@schematics/angular:resolver": { + "skipTests": true + }, + "@schematics/angular:service": { + "skipTests": true } }, "root": "", @@ -15,21 +45,32 @@ "prefix": "app", "architect": { "build": { - "builder": "@angular-devkit/build-angular:application", + "builder": "@angular/build:application", "options": { - "outputPath": "dist/selection", - "index": "src/index.html", "browser": "src/main.ts", - "polyfills": ["zone.js"], "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": ["src/favicon.ico", "src/assets"], - "styles": ["src/styles.scss"], - "scripts": [] + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": ["src/styles.css"] }, "configurations": { "production": { - "budgets": [], + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kB", + "maximumError": "8kB" + } + ], "outputHashing": "all" }, "development": { @@ -41,41 +82,18 @@ "defaultConfiguration": "production" }, "serve": { - "builder": "@angular-devkit/build-angular:dev-server", + "builder": "@angular/build:dev-server", "configurations": { "production": { - "buildTarget": "selection:build:production" + "buildTarget": "row-selection:build:production" }, "development": { - "buildTarget": "selection:build:development" + "buildTarget": "row-selection:build:development" } }, "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "buildTarget": "selection:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "polyfills": ["zone.js", "zone.js/testing"], - "tsConfig": "tsconfig.spec.json", - "inlineStyleLanguage": "scss", - "assets": ["src/favicon.ico", "src/assets"], - "styles": ["src/styles.scss"], - "scripts": [] - } } } } - }, - "cli": { - "analytics": false, - "cache": { - "enabled": false - } } } diff --git a/examples/angular/row-selection/package.json b/examples/angular/row-selection/package.json index da1b83e281..eba52a995a 100644 --- a/examples/angular/row-selection/package.json +++ b/examples/angular/row-selection/package.json @@ -1,39 +1,32 @@ { - "name": "tanstack-table-example-angular-row-selection", - "version": "0.0.0", + "name": "tanstack-angular-table-example-row-selection", "scripts": { "ng": "ng", - "start": "ng serve", + "start": "ng serve --port 5173", + "dev": "ng serve --port 5173", "build": "ng build", "watch": "ng build --watch --configuration development", - "test": "ng test" + "lint": "eslint ./src" }, "private": true, + "packageManager": "pnpm@11.0.9", "dependencies": { - "@angular/animations": "^17.3.9", - "@angular/common": "^17.3.9", - "@angular/compiler": "^17.3.9", - "@angular/core": "^17.3.9", - "@angular/forms": "^17.3.9", - "@angular/platform-browser": "^17.3.9", - "@angular/platform-browser-dynamic": "^17.3.9", - "@faker-js/faker": "^8.4.1", - "@tanstack/angular-table": "^8.20.5", - "rxjs": "~7.8.1", - "tslib": "^2.6.2", - "zone.js": "~0.14.4" + "@angular/cdk": "^21.2.10", + "@angular/common": "^21.2.12", + "@angular/compiler": "^21.2.12", + "@angular/core": "^21.2.12", + "@angular/forms": "^21.2.12", + "@angular/platform-browser": "^21.2.12", + "@angular/router": "^21.2.12", + "@faker-js/faker": "^10.4.0", + "@tanstack/angular-table": "^9.0.0-alpha.45", + "rxjs": "~7.8.2", + "tslib": "^2.8.1" }, "devDependencies": { - "@angular-devkit/build-angular": "^17.3.8", - "@angular/cli": "^17.3.8", - "@angular/compiler-cli": "^17.3.9", - "@types/jasmine": "~5.1.4", - "jasmine-core": "~5.1.2", - "karma": "~6.4.3", - "karma-chrome-launcher": "~3.2.0", - "karma-coverage": "~2.2.1", - "karma-jasmine": "~5.1.0", - "karma-jasmine-html-reporter": "~2.1.0", - "typescript": "5.4.5" + "@angular/build": "^21.2.10", + "@angular/cli": "^21.2.10", + "@angular/compiler-cli": "^21.2.12", + "typescript": "6.0.3" } } diff --git a/examples/angular/row-selection/public/favicon.ico b/examples/angular/row-selection/public/favicon.ico new file mode 100644 index 0000000000..57614f9c96 Binary files /dev/null and b/examples/angular/row-selection/public/favicon.ico differ diff --git a/examples/angular/row-selection/src/app/app.component.html b/examples/angular/row-selection/src/app/app.component.html deleted file mode 100644 index c2dfb725f7..0000000000 --- a/examples/angular/row-selection/src/app/app.component.html +++ /dev/null @@ -1,152 +0,0 @@ -
-
- - - - @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { - - @for (header of headerGroup.headers; track header.id) { - - } - - } - - - @for (row of table.getRowModel().rows; track row.id) { - - @for (cell of row.getVisibleCells(); track cell.id) { - - } - - } - - - - - - - -
- @if (!header.isPlaceholder) { - - {{ headerCell }} - - - @if (header.column.getCanFilter()) { -
- -
- } - } -
- - {{ renderCell }} - -
- - - Page Rows ({{ table.getRowModel().rows.length }}) -
- -
-
- - - - - -
Page
- - {{ table.getState().pagination.pageIndex + 1 }} of - {{ table.getPageCount() }} - -
- - | Go to page: - - - - -
-
-
- {{ rowSelectionLength() }} of - {{ table.getPreFilteredRowModel().rows.length }} Total Rows -
-
-
-
- -
-
- -
-
- -
{{ stringifiedRowSelection() }}
-
-
- - - Age 🥳 - diff --git a/examples/angular/row-selection/src/app/app.component.ts b/examples/angular/row-selection/src/app/app.component.ts deleted file mode 100644 index 8711fd3959..0000000000 --- a/examples/angular/row-selection/src/app/app.component.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - computed, - signal, - TemplateRef, - viewChild, -} from '@angular/core' -import { - ColumnDef, - createAngularTable, - FlexRenderComponent, - FlexRenderDirective, - getCoreRowModel, - getFilteredRowModel, - getPaginationRowModel, - RowSelectionState, -} from '@tanstack/angular-table' -import { FilterComponent } from './filter' -import { makeData, type Person } from './makeData' -import { FormsModule } from '@angular/forms' -import { - TableHeadSelectionComponent, - TableRowSelectionComponent, -} from './selection-column.component' - -@Component({ - selector: 'app-root', - standalone: true, - imports: [FilterComponent, FlexRenderDirective, FormsModule], - templateUrl: './app.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AppComponent { - private readonly rowSelection = signal({}) - readonly globalFilter = signal('') - readonly data = signal(makeData(10_000)) - - readonly ageHeaderCell = - viewChild.required>('ageHeaderCell') - - readonly columns: ColumnDef[] = [ - { - id: 'select', - header: () => { - return new FlexRenderComponent(TableHeadSelectionComponent) - }, - cell: () => { - return new FlexRenderComponent(TableRowSelectionComponent) - }, - }, - { - header: 'Name', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'firstName', - cell: info => info.getValue(), - footer: props => props.column.id, - header: 'First name', - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - cell: info => info.getValue(), - header: () => 'Last Name', - footer: props => props.column.id, - }, - ], - }, - { - header: 'Info', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'age', - header: () => this.ageHeaderCell(), - footer: props => props.column.id, - }, - { - header: 'More Info', - columns: [ - { - accessorKey: 'visits', - header: () => 'Visits', - footer: props => props.column.id, - }, - { - accessorKey: 'status', - header: 'Status', - footer: props => props.column.id, - }, - { - accessorKey: 'progress', - header: 'Profile Progress', - footer: props => props.column.id, - }, - ], - }, - ], - }, - ] - - table = createAngularTable(() => ({ - data: this.data(), - columns: this.columns, - state: { - rowSelection: this.rowSelection(), - }, - enableRowSelection: true, // enable row selection for all rows - // enableRowSelection: row => row.original.age > 18, // or enable row selection conditionally per row - onRowSelectionChange: updaterOrValue => { - this.rowSelection.set( - typeof updaterOrValue === 'function' - ? updaterOrValue(this.rowSelection()) - : updaterOrValue - ) - }, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getPaginationRowModel: getPaginationRowModel(), - debugTable: true, - })) - - readonly stringifiedRowSelection = computed(() => - JSON.stringify(this.rowSelection(), null, 2) - ) - - readonly rowSelectionLength = computed( - () => Object.keys(this.rowSelection()).length - ) - - onPageInputChange(event: Event): void { - const inputElement = event.target as HTMLInputElement - const page = inputElement.value ? Number(inputElement.value) - 1 : 0 - this.table.setPageIndex(page) - } - - onPageSizeChange(event: any): void { - this.table.setPageSize(Number(event.target.value)) - } - - logSelectedFlatRows(): void { - console.info( - 'table.getSelectedRowModel().flatRows', - this.table.getSelectedRowModel().flatRows - ) - } - - refreshData(): void { - this.data.set(makeData(10_000)) - } -} diff --git a/examples/angular/row-selection/src/app/app.config.ts b/examples/angular/row-selection/src/app/app.config.ts index f27099f33c..cbb47d366c 100644 --- a/examples/angular/row-selection/src/app/app.config.ts +++ b/examples/angular/row-selection/src/app/app.config.ts @@ -1,5 +1,6 @@ -import { ApplicationConfig } from '@angular/core' +import { provideBrowserGlobalErrorListeners } from '@angular/core' +import type { ApplicationConfig } from '@angular/core' export const appConfig: ApplicationConfig = { - providers: [], + providers: [provideBrowserGlobalErrorListeners()], } diff --git a/examples/angular/row-selection/src/app/app.html b/examples/angular/row-selection/src/app/app.html new file mode 100644 index 0000000000..6d27d1c5e4 --- /dev/null +++ b/examples/angular/row-selection/src/app/app.html @@ -0,0 +1,134 @@ +
+ + +
+ + +
+ + + + @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { + + @for (header of headerGroup.headers; track header.id) { + + } + + } + + + @for (row of table.getRowModel().rows; track row.id) { + + @for (cell of row.getAllCells(); track cell.id) { + + } + + } + + + + + + + +
+ @if (!header.isPlaceholder) { + + {{ headerCell }} + + + @if (header.column.getCanFilter()) { +
+ +
+ } + } +
+ + {{ renderCell }} + +
+ + + Page Rows ({{ table.getRowModel().rows.length.toLocaleString() }}) +
+ +
+
+ + + + + +
Page
+ + {{ (paginationState().pageIndex + 1).toLocaleString() }} of + {{ table.getPageCount().toLocaleString() }} + +
+ + | Go to page: + + + + +
+
+
+ {{ rowSelectionLength().toLocaleString() }} of + {{ table.getPreFilteredRowModel().rows.length.toLocaleString() }} Total Rows +
+
+
+
+ +
+
+ +
{{ stringifiedRowSelection() }}
+
+
diff --git a/examples/angular/row-selection/src/app/app.ts b/examples/angular/row-selection/src/app/app.ts new file mode 100644 index 0000000000..e495508fc0 --- /dev/null +++ b/examples/angular/row-selection/src/app/app.ts @@ -0,0 +1,146 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + signal, +} from '@angular/core' +import { + FlexRender, + TanStackTable, + flexRenderComponent, + shallow, +} from '@tanstack/angular-table' +import { TableFilter } from './table-filter/table-filter' +import { makeData } from './makeData' +import { + TableHeaderSelection, + TableRowSelection, +} from './selection-column/selection-column' +import { createAppColumnHelper, injectTable } from './table' +import type { Person } from './makeData' +import type { RowSelectionState } from '@tanstack/angular-table' + +const columnHelper = createAppColumnHelper() + +@Component({ + selector: 'app-root', + standalone: true, + imports: [TableFilter, FlexRender, TanStackTable], + templateUrl: './app.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class App { + private readonly rowSelection = signal({}) + readonly globalFilter = signal('') + readonly data = signal(makeData(1_000)) + readonly enableRowSelection = signal(true) + + readonly columns = columnHelper.columns([ + columnHelper.display({ + id: 'select', + header: () => flexRenderComponent(TableHeaderSelection), + cell: () => flexRenderComponent(TableRowSelection), + }), + columnHelper.group({ + header: 'Name', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + header: () => 'First name', + }), + columnHelper.accessor('lastName', { + cell: (info) => info.getValue(), + header: () => 'Last Name', + footer: (props) => props.column.id, + }), + ]), + }), + columnHelper.group({ + header: 'Info', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('age', { + header: () => `Age 🥳`, + footer: (props) => props.column.id, + }), + columnHelper.group({ + header: 'More Info', + columns: columnHelper.columns([ + columnHelper.accessor((row) => row.visits, { + id: 'visits', + header: () => 'Visits', + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.status, { + id: 'status', + header: () => 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.progress, { + id: 'progress', + header: 'Profile Progress', + footer: (props) => props.column.id, + }), + ]), + }), + ]), + }), + ]) + + readonly table = injectTable(() => ({ + debugTable: true, + data: this.data(), + columns: this.columns, + state: { + rowSelection: this.rowSelection(), + }, + + enableRowSelection: this.enableRowSelection(), // enable row selection for all rows + // enableRowSelection: row => row.original.age > 18, // or enable row selection conditionally per row + onRowSelectionChange: (updaterOrValue) => { + this.rowSelection.set( + typeof updaterOrValue === 'function' + ? updaterOrValue(this.rowSelection()) + : updaterOrValue, + ) + }, + })) + + readonly paginationState = computed(() => this.table.atoms.pagination.get(), { + equal: shallow, + }) + + readonly stringifiedRowSelection = computed(() => + JSON.stringify(this.rowSelection(), null, 2), + ) + + readonly rowSelectionLength = computed( + () => Object.keys(this.rowSelection()).length, + ) + + onPageInputChange(event: Event): void { + const inputElement = event.target as HTMLInputElement + const page = inputElement.value ? Number(inputElement.value) - 1 : 0 + this.table.setPageIndex(page) + } + + onPageSizeChange(event: any): void { + this.table.setPageSize(Number(event.target.value)) + } + + logSelectedFlatRows(): void { + console.info( + 'table.getSelectedRowModel().flatRows', + this.table.getSelectedRowModel().flatRows, + ) + } + + refreshData = () => this.data.set(makeData(1_000)) + stressTest = () => this.data.set(makeData(200_000)) + + toggleEnableRowSelection() { + this.enableRowSelection.update((value) => !value) + } +} diff --git a/examples/angular/row-selection/src/app/filter.ts b/examples/angular/row-selection/src/app/filter.ts deleted file mode 100644 index cf0b567b36..0000000000 --- a/examples/angular/row-selection/src/app/filter.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { CommonModule } from '@angular/common' -import { Component, input, OnInit } from '@angular/core' -import { Column } from '@tanstack/angular-table' -import type { Table } from '@tanstack/angular-table' - -@Component({ - selector: 'app-table-filter', - template: ` @if (columnType) { - @if (columnType == 'number') { -
- - -
- } @else { - - } - }`, - standalone: true, - imports: [CommonModule], -}) -export class FilterComponent implements OnInit { - column = input.required>() - - table = input.required>() - - columnType!: string - - ngOnInit() { - this.columnType = typeof this.table() - .getPreFilteredRowModel() - .flatRows[0]?.getValue(this.column().id) - } - - getMinValue() { - const minValue = this.column().getFilterValue() as any - - return (minValue?.[0] ?? '') as string - } - - getMaxValue() { - const maxValue = this.column().getFilterValue() as any - return (maxValue?.[1] ?? '') as string - } - - updateMinFilterValue(newValue: string): void { - this.column().setFilterValue((old: any) => { - return [newValue, old?.[1]] - }) - } - - updateMaxFilterValue(newValue: string): void { - this.column().setFilterValue((old: any) => [old?.[0], newValue]) - } -} diff --git a/examples/angular/row-selection/src/app/makeData.ts b/examples/angular/row-selection/src/app/makeData.ts index 331dd1eb19..b9fb014aba 100644 --- a/examples/angular/row-selection/src/app/makeData.ts +++ b/examples/angular/row-selection/src/app/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,13 +29,13 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] return range(len).map((d): Person => { return { ...newPerson(), diff --git a/examples/angular/row-selection/src/app/selection-column.component.ts b/examples/angular/row-selection/src/app/selection-column.component.ts deleted file mode 100644 index b4f3e1c008..0000000000 --- a/examples/angular/row-selection/src/app/selection-column.component.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { - type CellContext, - type HeaderContext, - injectFlexRenderContext, -} from '@tanstack/angular-table' -import { ChangeDetectionStrategy, Component } from '@angular/core' - -@Component({ - template: ` - - `, - host: { - class: 'px-1 block', - }, - standalone: true, - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class TableHeadSelectionComponent { - context = injectFlexRenderContext>() -} - -@Component({ - template: ` - - `, - host: { - class: 'px-1 block', - }, - standalone: true, - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class TableRowSelectionComponent { - context = injectFlexRenderContext>() -} diff --git a/examples/angular/row-selection/src/app/selection-column/selection-column.ts b/examples/angular/row-selection/src/app/selection-column/selection-column.ts new file mode 100644 index 0000000000..94ef240acd --- /dev/null +++ b/examples/angular/row-selection/src/app/selection-column/selection-column.ts @@ -0,0 +1,46 @@ +import { injectTableCellContext } from '@tanstack/angular-table' +import { ChangeDetectionStrategy, Component, computed } from '@angular/core' +import { + injectFlexRenderCellContext, + injectFlexRenderHeaderContext, +} from '../table' + +@Component({ + template: ` + + `, + host: { + class: 'selection-cell', + }, + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TableHeaderSelection { + context = injectFlexRenderHeaderContext() +} + +@Component({ + template: ` + + `, + host: { + class: 'selection-cell', + }, + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TableRowSelection { + readonly cell = injectTableCellContext() + readonly row = computed(() => this.cell().row) + readonly context = injectFlexRenderCellContext() +} diff --git a/examples/angular/row-selection/src/app/table-filter/table-filter.ts b/examples/angular/row-selection/src/app/table-filter/table-filter.ts new file mode 100644 index 0000000000..adf8505a76 --- /dev/null +++ b/examples/angular/row-selection/src/app/table-filter/table-filter.ts @@ -0,0 +1,72 @@ +import { Component, computed, input } from '@angular/core' +import { injectTableContext } from '../table' +import type { Column } from '@tanstack/angular-table' + +@Component({ + selector: 'app-table-filter', + template: ` + @let columnType = this.columnType(); + + @if (columnType == 'number') { +
+ + +
+ } @else { + + } + `, + standalone: true, +}) +export class TableFilter { + readonly column = input.required>() + readonly table = injectTableContext() + + readonly columnType = computed(() => { + return typeof this.table() + .getPreFilteredRowModel() + .flatRows[0]?.getValue(this.column().id) + }) + + getMinValue() { + const minValue = this.column().getFilterValue() + + return (minValue?.[0] ?? '') as string + } + + getMaxValue() { + const maxValue = this.column().getFilterValue() + return (maxValue?.[1] ?? '') as string + } + + updateMinFilterValue(newValue: string): void { + this.column().setFilterValue((old: any) => { + return [newValue, old?.[1]] + }) + } + + updateMaxFilterValue(newValue: string): void { + this.column().setFilterValue((old: any) => [old?.[0], newValue]) + } +} diff --git a/examples/angular/row-selection/src/app/table.ts b/examples/angular/row-selection/src/app/table.ts new file mode 100644 index 0000000000..3810a042ad --- /dev/null +++ b/examples/angular/row-selection/src/app/table.ts @@ -0,0 +1,31 @@ +import { + columnFilteringFeature, + createFilteredRowModel, + createPaginatedRowModel, + createTableHook, + filterFns, + rowPaginationFeature, + rowSelectionFeature, + tableFeatures, +} from '@tanstack/angular-table' + +const _features = tableFeatures({ + columnFilteringFeature, + rowPaginationFeature, + rowSelectionFeature, +}) + +export const { + injectAppTable: injectTable, + injectTableContext, + createAppColumnHelper, + injectFlexRenderCellContext, + injectFlexRenderHeaderContext, +} = createTableHook({ + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + }, + debugTable: true, +}) diff --git a/examples/angular/row-selection/src/assets/.gitkeep b/examples/angular/row-selection/src/assets/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/examples/angular/row-selection/src/index.html b/examples/angular/row-selection/src/index.html index 27917d2b28..4f40d38cef 100644 --- a/examples/angular/row-selection/src/index.html +++ b/examples/angular/row-selection/src/index.html @@ -6,7 +6,6 @@ - diff --git a/examples/angular/row-selection/src/main.ts b/examples/angular/row-selection/src/main.ts index 0c3b92057c..8192dca694 100644 --- a/examples/angular/row-selection/src/main.ts +++ b/examples/angular/row-selection/src/main.ts @@ -1,5 +1,5 @@ import { bootstrapApplication } from '@angular/platform-browser' import { appConfig } from './app/app.config' -import { AppComponent } from './app/app.component' +import { App } from './app/app' -bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err)) +bootstrapApplication(App, appConfig).catch((err) => console.error(err)) diff --git a/examples/angular/row-selection/src/styles.css b/examples/angular/row-selection/src/styles.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/angular/row-selection/src/styles.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/angular/row-selection/src/styles.scss b/examples/angular/row-selection/src/styles.scss deleted file mode 100644 index 43c09e0f6b..0000000000 --- a/examples/angular/row-selection/src/styles.scss +++ /dev/null @@ -1,26 +0,0 @@ -html { - font-family: sans-serif; - font-size: 14px; -} - -table { - border: 1px solid lightgray; -} - -tbody { - border-bottom: 1px solid lightgray; -} - -th { - border-bottom: 1px solid lightgray; - border-right: 1px solid lightgray; - padding: 2px 4px; -} - -tfoot { - color: gray; -} - -tfoot th { - font-weight: normal; -} diff --git a/examples/angular/row-selection/tsconfig.app.json b/examples/angular/row-selection/tsconfig.app.json index 84f1f992d2..a0dcc37c60 100644 --- a/examples/angular/row-selection/tsconfig.app.json +++ b/examples/angular/row-selection/tsconfig.app.json @@ -1,10 +1,11 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/app", "types": [] }, - "files": ["src/main.ts"], - "include": ["src/**/*.d.ts"] + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] } diff --git a/examples/angular/row-selection/tsconfig.json b/examples/angular/row-selection/tsconfig.json index fd2d87ac26..32789cdc2b 100644 --- a/examples/angular/row-selection/tsconfig.json +++ b/examples/angular/row-selection/tsconfig.json @@ -1,30 +1,23 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ { "compileOnSave": false, "compilerOptions": { - "outDir": "./dist/out-tsc", - "forceConsistentCasingInFileNames": true, "strict": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "skipLibCheck": true, - "esModuleInterop": true, - "sourceMap": true, - "declaration": false, + "isolatedModules": true, "experimentalDecorators": true, - "moduleResolution": "node", "importHelpers": true, "target": "ES2022", - "module": "ES2022", - "useDefineForClassFields": false, - "lib": ["ES2022", "dom"] + "module": "preserve" }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": true, "strictInputAccessModifiers": true, "strictTemplates": true - } + }, + "include": ["**/*.ts", "vite.config.js", "vite.config.ts"] } diff --git a/examples/angular/row-selection/tsconfig.spec.json b/examples/angular/row-selection/tsconfig.spec.json deleted file mode 100644 index 47e3dd7551..0000000000 --- a/examples/angular/row-selection/tsconfig.spec.json +++ /dev/null @@ -1,9 +0,0 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/spec", - "types": ["jasmine"] - }, - "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] -} diff --git a/examples/angular/signal-input/.devcontainer/devcontainer.json b/examples/angular/signal-input/.devcontainer/devcontainer.json deleted file mode 100644 index 36f47d8762..0000000000 --- a/examples/angular/signal-input/.devcontainer/devcontainer.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "Node.js", - "image": "mcr.microsoft.com/devcontainers/javascript-node:18" -} diff --git a/examples/angular/signal-input/.editorconfig b/examples/angular/signal-input/.editorconfig deleted file mode 100644 index 59d9a3a3e7..0000000000 --- a/examples/angular/signal-input/.editorconfig +++ /dev/null @@ -1,16 +0,0 @@ -# Editor configuration, see https://editorconfig.org -root = true - -[*] -charset = utf-8 -indent_style = space -indent_size = 2 -insert_final_newline = true -trim_trailing_whitespace = true - -[*.ts] -quote_type = single - -[*.md] -max_line_length = off -trim_trailing_whitespace = false diff --git a/examples/angular/signal-input/.gitignore b/examples/angular/signal-input/.gitignore index 0711527ef9..854acd5fc0 100644 --- a/examples/angular/signal-input/.gitignore +++ b/examples/angular/signal-input/.gitignore @@ -1,4 +1,4 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. # Compiled output /dist @@ -26,6 +26,7 @@ yarn-error.log !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json +!.vscode/mcp.json .history/* # Miscellaneous @@ -36,6 +37,7 @@ yarn-error.log /libpeerconnection.log testem.log /typings +__screenshots__/ # System files .DS_Store diff --git a/examples/angular/signal-input/README.md b/examples/angular/signal-input/README.md index 8f0c2d4c12..c255261608 100644 --- a/examples/angular/signal-input/README.md +++ b/examples/angular/signal-input/README.md @@ -1,27 +1,59 @@ -# Grouping +# Signal Input -This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.1.2. +This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.1.0. ## Development server -Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. +To start a local development server, run: + +```bash +ng serve +``` + +Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files. ## Code scaffolding -Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. +Angular CLI includes powerful code scaffolding tools. To generate a new component, run: + +```bash +ng generate component component-name +``` + +For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run: + +```bash +ng generate --help +``` -## Build +## Building -Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. +To build the project run: + +```bash +ng build +``` + +This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed. ## Running unit tests -Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). +To execute unit tests with the [Vitest](https://vitest.dev/) test runner, use the following command: + +```bash +ng test +``` ## Running end-to-end tests -Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. +For end-to-end (e2e) testing, run: + +```bash +ng e2e +``` + +Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs. -## Further help +## Additional Resources -To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. +For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. diff --git a/examples/angular/signal-input/angular.json b/examples/angular/signal-input/angular.json index 1ce36507d6..df64e1bd17 100644 --- a/examples/angular/signal-input/angular.json +++ b/examples/angular/signal-input/angular.json @@ -1,13 +1,43 @@ { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, + "cli": { + "packageManager": "pnpm", + "analytics": false, + "cache": { + "enabled": false + } + }, "newProjectRoot": "projects", "projects": { - "grouping": { + "signal-input": { "projectType": "application", "schematics": { "@schematics/angular:component": { - "style": "scss" + "inlineTemplate": true, + "inlineStyle": true, + "skipTests": true + }, + "@schematics/angular:class": { + "skipTests": true + }, + "@schematics/angular:directive": { + "skipTests": true + }, + "@schematics/angular:guard": { + "skipTests": true + }, + "@schematics/angular:interceptor": { + "skipTests": true + }, + "@schematics/angular:pipe": { + "skipTests": true + }, + "@schematics/angular:resolver": { + "skipTests": true + }, + "@schematics/angular:service": { + "skipTests": true } }, "root": "", @@ -15,21 +45,32 @@ "prefix": "app", "architect": { "build": { - "builder": "@angular-devkit/build-angular:application", + "builder": "@angular/build:application", "options": { - "outputPath": "dist/signal-input", - "index": "src/index.html", "browser": "src/main.ts", - "polyfills": ["zone.js"], "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": ["src/favicon.ico", "src/assets"], - "styles": ["src/styles.scss"], - "scripts": [] + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": ["src/styles.css"] }, "configurations": { "production": { - "budgets": [], + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kB", + "maximumError": "8kB" + } + ], "outputHashing": "all" }, "development": { @@ -41,41 +82,18 @@ "defaultConfiguration": "production" }, "serve": { - "builder": "@angular-devkit/build-angular:dev-server", + "builder": "@angular/build:dev-server", "configurations": { "production": { - "buildTarget": "grouping:build:production" + "buildTarget": "signal-input:build:production" }, "development": { - "buildTarget": "grouping:build:development" + "buildTarget": "signal-input:build:development" } }, "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "buildTarget": "grouping:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "polyfills": ["zone.js", "zone.js/testing"], - "tsConfig": "tsconfig.spec.json", - "inlineStyleLanguage": "scss", - "assets": ["src/favicon.ico", "src/assets"], - "styles": ["src/styles.scss"], - "scripts": [] - } } } } - }, - "cli": { - "analytics": false, - "cache": { - "enabled": false - } } } diff --git a/examples/angular/signal-input/package.json b/examples/angular/signal-input/package.json index dee398f483..cb9e8bc836 100644 --- a/examples/angular/signal-input/package.json +++ b/examples/angular/signal-input/package.json @@ -1,38 +1,32 @@ { - "name": "tanstack-table-example-angular-signal-input", - "version": "0.0.0", + "name": "tanstack-angular-table-example-signal-input", "scripts": { "ng": "ng", - "start": "ng serve", + "start": "ng serve --port 5173", + "dev": "ng serve --port 5173", "build": "ng build", "watch": "ng build --watch --configuration development", - "test": "ng test" + "lint": "eslint ./src" }, "private": true, + "packageManager": "pnpm@11.0.9", "dependencies": { - "@angular/animations": "^17.3.9", - "@angular/common": "^17.3.9", - "@angular/compiler": "^17.3.9", - "@angular/core": "^17.3.9", - "@angular/platform-browser": "^17.3.9", - "@angular/platform-browser-dynamic": "^17.3.9", - "@faker-js/faker": "^8.4.1", - "@tanstack/angular-table": "^8.20.5", - "rxjs": "~7.8.1", - "zone.js": "~0.14.4" + "@angular/cdk": "^21.2.10", + "@angular/common": "^21.2.12", + "@angular/compiler": "^21.2.12", + "@angular/core": "^21.2.12", + "@angular/forms": "^21.2.12", + "@angular/platform-browser": "^21.2.12", + "@angular/router": "^21.2.12", + "@faker-js/faker": "^10.4.0", + "@tanstack/angular-table": "^9.0.0-alpha.45", + "rxjs": "~7.8.2", + "tslib": "^2.8.1" }, "devDependencies": { - "@angular-devkit/build-angular": "^17.3.8", - "@angular/cli": "^17.3.8", - "@angular/compiler-cli": "^17.3.9", - "@types/jasmine": "~5.1.4", - "jasmine-core": "~5.1.2", - "karma": "~6.4.3", - "karma-chrome-launcher": "~3.2.0", - "karma-coverage": "~2.2.1", - "karma-jasmine": "~5.1.0", - "karma-jasmine-html-reporter": "~2.1.0", - "tslib": "^2.6.2", - "typescript": "5.4.5" + "@angular/build": "^21.2.10", + "@angular/cli": "^21.2.10", + "@angular/compiler-cli": "^21.2.12", + "typescript": "6.0.3" } } diff --git a/examples/angular/signal-input/public/favicon.ico b/examples/angular/signal-input/public/favicon.ico new file mode 100644 index 0000000000..57614f9c96 Binary files /dev/null and b/examples/angular/signal-input/public/favicon.ico differ diff --git a/examples/angular/signal-input/src/app/app.component.html b/examples/angular/signal-input/src/app/app.component.html deleted file mode 100644 index c55dcbfe3a..0000000000 --- a/examples/angular/signal-input/src/app/app.component.html +++ /dev/null @@ -1,18 +0,0 @@ -
-
- - -
- - - - - -
-
diff --git a/examples/angular/signal-input/src/app/app.component.ts b/examples/angular/signal-input/src/app/app.component.ts deleted file mode 100644 index ac4f08a53b..0000000000 --- a/examples/angular/signal-input/src/app/app.component.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - effect, - signal, -} from '@angular/core' -import { type PaginationState } from '@tanstack/angular-table' -import { makeData } from './makeData' -import { PersonTableComponent } from './person-table/person-table.component' - -@Component({ - selector: 'app-root', - standalone: true, - imports: [PersonTableComponent], - templateUrl: './app.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AppComponent { - data = signal(makeData(10000)) - pagination = signal({ - pageIndex: 0, - pageSize: 5, - }) - - refreshData() { - this.data.set(makeData(10000)) - } - - previousPage(): void { - this.pagination.update(pagination => ({ - ...pagination, - pageIndex: pagination.pageIndex - 1, - })) - } - - nextPage(): void { - this.pagination.update(pagination => ({ - ...pagination, - pageIndex: pagination.pageIndex + 1, - })) - } -} diff --git a/examples/angular/signal-input/src/app/app.config.ts b/examples/angular/signal-input/src/app/app.config.ts index f27099f33c..cbb47d366c 100644 --- a/examples/angular/signal-input/src/app/app.config.ts +++ b/examples/angular/signal-input/src/app/app.config.ts @@ -1,5 +1,6 @@ -import { ApplicationConfig } from '@angular/core' +import { provideBrowserGlobalErrorListeners } from '@angular/core' +import type { ApplicationConfig } from '@angular/core' export const appConfig: ApplicationConfig = { - providers: [], + providers: [provideBrowserGlobalErrorListeners()], } diff --git a/examples/angular/signal-input/src/app/app.html b/examples/angular/signal-input/src/app/app.html new file mode 100644 index 0000000000..cb8d3ba1c8 --- /dev/null +++ b/examples/angular/signal-input/src/app/app.html @@ -0,0 +1,14 @@ +
+ + +
+ + +
+ + + +
+
diff --git a/examples/angular/signal-input/src/app/app.ts b/examples/angular/signal-input/src/app/app.ts new file mode 100644 index 0000000000..0deafd91bc --- /dev/null +++ b/examples/angular/signal-input/src/app/app.ts @@ -0,0 +1,35 @@ +import { ChangeDetectionStrategy, Component, signal } from '@angular/core' +import { makeData } from './makeData' +import { PersonTable } from './person-table/person-table' +import type { PaginationState } from '@tanstack/angular-table' + +@Component({ + selector: 'app-root', + imports: [PersonTable], + templateUrl: './app.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class App { + data = signal(makeData(1_000)) + pagination = signal({ + pageIndex: 0, + pageSize: 5, + }) + + refreshData = () => this.data.set(makeData(1_000)) + stressTest = () => this.data.set(makeData(200_000)) + + previousPage(): void { + this.pagination.update((pagination) => ({ + ...pagination, + pageIndex: pagination.pageIndex - 1, + })) + } + + nextPage(): void { + this.pagination.update((pagination) => ({ + ...pagination, + pageIndex: pagination.pageIndex + 1, + })) + } +} diff --git a/examples/angular/signal-input/src/app/columns.ts b/examples/angular/signal-input/src/app/columns.ts deleted file mode 100644 index 0401539502..0000000000 --- a/examples/angular/signal-input/src/app/columns.ts +++ /dev/null @@ -1,9 +0,0 @@ -export type Person = { - firstName: string - lastName: string - age: number - visits: number - progress: number - status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] -} diff --git a/examples/angular/signal-input/src/app/makeData.ts b/examples/angular/signal-input/src/app/makeData.ts index 331dd1eb19..b9fb014aba 100644 --- a/examples/angular/signal-input/src/app/makeData.ts +++ b/examples/angular/signal-input/src/app/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,13 +29,13 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] return range(len).map((d): Person => { return { ...newPerson(), diff --git a/examples/angular/signal-input/src/app/person-table/person-table.component.html b/examples/angular/signal-input/src/app/person-table/person-table.component.html deleted file mode 100644 index 4f5acd2f95..0000000000 --- a/examples/angular/signal-input/src/app/person-table/person-table.component.html +++ /dev/null @@ -1,100 +0,0 @@ - - - @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { - - @for (header of headerGroup.headers; track header.id) { - - } - - } - - - @for (row of table.getRowModel().rows; track row.id) { - - @for (cell of row.getVisibleCells(); track cell.id) { - - } - - } - -
- @if (!header.isPlaceholder) { - - {{ header }} - - } -
- - {{ cell }} - -
- -
-
- - - - - -
Page
- - {{ table.getState().pagination.pageIndex + 1 }} of - {{ table.getPageCount() }} - -
- - | Go to page: - - - - -
-
{{ table.getRowModel().rows.length }} Rows
diff --git a/examples/angular/signal-input/src/app/person-table/person-table.component.ts b/examples/angular/signal-input/src/app/person-table/person-table.component.ts deleted file mode 100644 index 29a8622ce9..0000000000 --- a/examples/angular/signal-input/src/app/person-table/person-table.component.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { ChangeDetectionStrategy, Component, input, model } from '@angular/core' -import type { Person } from '../makeData' -import { - ColumnDef, - createAngularTable, - FlexRenderDirective, - getCoreRowModel, - getExpandedRowModel, - getFilteredRowModel, - getGroupedRowModel, - getPaginationRowModel, - PaginationState, -} from '@tanstack/angular-table' - -@Component({ - selector: 'app-person-table', - templateUrl: 'person-table.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - standalone: true, - imports: [FlexRenderDirective], -}) -export class PersonTableComponent { - readonly data = input.required() - - readonly pagination = model.required() - - readonly columns: ColumnDef[] = [ - { - accessorKey: 'firstName', - header: 'First Name', - cell: info => info.getValue(), - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - header: () => `Last Name`, - cell: info => info.getValue(), - }, - ] - - table = createAngularTable(() => { - return { - data: this.data(), - columns: this.columns, - state: { - pagination: this.pagination(), - }, - onPaginationChange: updaterOrValue => { - typeof updaterOrValue === 'function' - ? this.pagination.update(updaterOrValue) - : this.pagination.set(updaterOrValue) - }, - getExpandedRowModel: getExpandedRowModel(), - getGroupedRowModel: getGroupedRowModel(), - getCoreRowModel: getCoreRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getFilteredRowModel: getFilteredRowModel(), - debugTable: true, - } - }) - - onPageInputChange(event: any): void { - const page = event.target.value ? Number(event.target.value) - 1 : 0 - this.table.setPageIndex(page) - } - - onPageSizeChange(event: any) { - this.table.setPageSize(Number(event.target.value)) - } -} diff --git a/examples/angular/signal-input/src/app/person-table/person-table.html b/examples/angular/signal-input/src/app/person-table/person-table.html new file mode 100644 index 0000000000..ea2246b62c --- /dev/null +++ b/examples/angular/signal-input/src/app/person-table/person-table.html @@ -0,0 +1,89 @@ + + + @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { + + @for (header of headerGroup.headers; track header.id) { + + } + + } + + + @for (row of table.getRowModel().rows; track row.id) { + + @for (cell of row.getVisibleCells(); track cell.id) { + + } + + } + +
+ @if (!header.isPlaceholder) { + + {{ header }} + + } +
+ + {{ cell }} + +
+ +
+
+ + + + + +
Page
+ + {{ (table.store.state.pagination.pageIndex + 1).toLocaleString() }} of + {{ table.getPageCount().toLocaleString() }} + +
+ + | Go to page: + + + + +
+
{{ table.getRowModel().rows.length.toLocaleString() }} Rows
diff --git a/examples/angular/signal-input/src/app/person-table/person-table.ts b/examples/angular/signal-input/src/app/person-table/person-table.ts new file mode 100644 index 0000000000..dfdc308161 --- /dev/null +++ b/examples/angular/signal-input/src/app/person-table/person-table.ts @@ -0,0 +1,71 @@ +import { ChangeDetectionStrategy, Component, input, model } from '@angular/core' +import { + FlexRender, + columnVisibilityFeature, + createPaginatedRowModel, + injectTable, + rowPaginationFeature, + tableFeatures, +} from '@tanstack/angular-table' +import type { ColumnDef, PaginationState } from '@tanstack/angular-table' +import type { Person } from '../makeData' + +const _features = tableFeatures({ + rowPaginationFeature, + columnVisibilityFeature, +}) + +@Component({ + selector: 'app-person-table', + templateUrl: 'person-table.html', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [FlexRender], +}) +export class PersonTable { + readonly data = input.required>() + + readonly pagination = model.required() + + readonly columns: Array> = [ + { + accessorKey: 'firstName', + header: 'First Name', + cell: (info) => info.getValue(), + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + header: () => `Last Name`, + cell: (info) => info.getValue(), + }, + ] + + readonly table = injectTable(() => { + return { + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + }, + data: this.data(), + columns: this.columns, + state: { + pagination: this.pagination(), + }, + onPaginationChange: (updaterOrValue) => { + typeof updaterOrValue === 'function' + ? this.pagination.update(updaterOrValue) + : this.pagination.set(updaterOrValue) + }, + debugTable: true, + } + }) + + onPageInputChange(event: any): void { + const page = event.target.value ? Number(event.target.value) - 1 : 0 + this.table.setPageIndex(page) + } + + onPageSizeChange(event: any) { + this.table.setPageSize(Number(event.target.value)) + } +} diff --git a/examples/angular/signal-input/src/assets/.gitkeep b/examples/angular/signal-input/src/assets/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/examples/angular/signal-input/src/index.html b/examples/angular/signal-input/src/index.html index af6a41707a..f10649fac0 100644 --- a/examples/angular/signal-input/src/index.html +++ b/examples/angular/signal-input/src/index.html @@ -2,15 +2,11 @@ - Grouping + Signal Input - - + diff --git a/examples/angular/signal-input/src/main.ts b/examples/angular/signal-input/src/main.ts index 0c3b92057c..8192dca694 100644 --- a/examples/angular/signal-input/src/main.ts +++ b/examples/angular/signal-input/src/main.ts @@ -1,5 +1,5 @@ import { bootstrapApplication } from '@angular/platform-browser' import { appConfig } from './app/app.config' -import { AppComponent } from './app/app.component' +import { App } from './app/app' -bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err)) +bootstrapApplication(App, appConfig).catch((err) => console.error(err)) diff --git a/examples/angular/signal-input/src/styles.css b/examples/angular/signal-input/src/styles.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/angular/signal-input/src/styles.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/angular/signal-input/src/styles.scss b/examples/angular/signal-input/src/styles.scss deleted file mode 100644 index 43c09e0f6b..0000000000 --- a/examples/angular/signal-input/src/styles.scss +++ /dev/null @@ -1,26 +0,0 @@ -html { - font-family: sans-serif; - font-size: 14px; -} - -table { - border: 1px solid lightgray; -} - -tbody { - border-bottom: 1px solid lightgray; -} - -th { - border-bottom: 1px solid lightgray; - border-right: 1px solid lightgray; - padding: 2px 4px; -} - -tfoot { - color: gray; -} - -tfoot th { - font-weight: normal; -} diff --git a/examples/angular/signal-input/tsconfig.app.json b/examples/angular/signal-input/tsconfig.app.json index 84f1f992d2..a0dcc37c60 100644 --- a/examples/angular/signal-input/tsconfig.app.json +++ b/examples/angular/signal-input/tsconfig.app.json @@ -1,10 +1,11 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/app", "types": [] }, - "files": ["src/main.ts"], - "include": ["src/**/*.d.ts"] + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] } diff --git a/examples/angular/signal-input/tsconfig.json b/examples/angular/signal-input/tsconfig.json index 82c63d482a..32789cdc2b 100644 --- a/examples/angular/signal-input/tsconfig.json +++ b/examples/angular/signal-input/tsconfig.json @@ -1,29 +1,23 @@ { "compileOnSave": false, "compilerOptions": { - "outDir": "./dist/out-tsc", - "forceConsistentCasingInFileNames": true, "strict": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "skipLibCheck": true, - "esModuleInterop": true, - "sourceMap": true, - "declaration": false, + "isolatedModules": true, "experimentalDecorators": true, - "moduleResolution": "node", "importHelpers": true, "target": "ES2022", - "module": "ES2022", - "useDefineForClassFields": false, - "lib": ["ES2022", "dom"] + "module": "preserve" }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": true, "strictInputAccessModifiers": true, "strictTemplates": true - } + }, + "include": ["**/*.ts", "vite.config.js", "vite.config.ts"] } diff --git a/examples/angular/signal-input/tsconfig.spec.json b/examples/angular/signal-input/tsconfig.spec.json deleted file mode 100644 index 47e3dd7551..0000000000 --- a/examples/angular/signal-input/tsconfig.spec.json +++ /dev/null @@ -1,9 +0,0 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/spec", - "types": ["jasmine"] - }, - "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] -} diff --git a/examples/angular/sub-components/.gitignore b/examples/angular/sub-components/.gitignore new file mode 100644 index 0000000000..854acd5fc0 --- /dev/null +++ b/examples/angular/sub-components/.gitignore @@ -0,0 +1,44 @@ +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out + +# Node +/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/mcp.json +.history/* + +# Miscellaneous +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings +__screenshots__/ + +# System files +.DS_Store +Thumbs.db diff --git a/examples/angular/sub-components/README.md b/examples/angular/sub-components/README.md new file mode 100644 index 0000000000..f0ab62065d --- /dev/null +++ b/examples/angular/sub-components/README.md @@ -0,0 +1,59 @@ +# Sub components + +This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.1.0. + +## Development server + +To start a local development server, run: + +```bash +ng serve +``` + +Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files. + +## Code scaffolding + +Angular CLI includes powerful code scaffolding tools. To generate a new component, run: + +```bash +ng generate component component-name +``` + +For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run: + +```bash +ng generate --help +``` + +## Building + +To build the project run: + +```bash +ng build +``` + +This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed. + +## Running unit tests + +To execute unit tests with the [Vitest](https://vitest.dev/) test runner, use the following command: + +```bash +ng test +``` + +## Running end-to-end tests + +For end-to-end (e2e) testing, run: + +```bash +ng e2e +``` + +Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs. + +## Additional Resources + +For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. diff --git a/examples/angular/sub-components/angular.json b/examples/angular/sub-components/angular.json new file mode 100644 index 0000000000..c2bdcab341 --- /dev/null +++ b/examples/angular/sub-components/angular.json @@ -0,0 +1,99 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "cli": { + "packageManager": "pnpm", + "analytics": false, + "cache": { + "enabled": false + } + }, + "newProjectRoot": "projects", + "projects": { + "sub-components": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "inlineTemplate": true, + "inlineStyle": true, + "skipTests": true + }, + "@schematics/angular:class": { + "skipTests": true + }, + "@schematics/angular:directive": { + "skipTests": true + }, + "@schematics/angular:guard": { + "skipTests": true + }, + "@schematics/angular:interceptor": { + "skipTests": true + }, + "@schematics/angular:pipe": { + "skipTests": true + }, + "@schematics/angular:resolver": { + "skipTests": true + }, + "@schematics/angular:service": { + "skipTests": true + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular/build:application", + "options": { + "browser": "src/main.ts", + "tsConfig": "tsconfig.app.json", + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": ["src/styles.css"] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kB", + "maximumError": "8kB" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular/build:dev-server", + "configurations": { + "production": { + "buildTarget": "sub-components:build:production" + }, + "development": { + "buildTarget": "sub-components:build:development" + } + }, + "defaultConfiguration": "development" + } + } + } + } +} diff --git a/examples/angular/sub-components/package.json b/examples/angular/sub-components/package.json new file mode 100644 index 0000000000..46f2cc40eb --- /dev/null +++ b/examples/angular/sub-components/package.json @@ -0,0 +1,31 @@ +{ + "name": "tanstack-angular-table-example-sub-components", + "scripts": { + "ng": "ng", + "start": "ng serve --port 5173", + "dev": "ng serve --port 5173", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "lint": "eslint ./src" + }, + "private": true, + "packageManager": "pnpm@11.0.9", + "dependencies": { + "@angular/common": "^21.2.12", + "@angular/compiler": "^21.2.12", + "@angular/core": "^21.2.12", + "@angular/forms": "^21.2.12", + "@angular/platform-browser": "^21.2.12", + "@angular/router": "^21.2.12", + "@faker-js/faker": "^10.4.0", + "@tanstack/angular-table": "^9.0.0-alpha.45", + "rxjs": "~7.8.2", + "tslib": "^2.8.1" + }, + "devDependencies": { + "@angular/build": "^21.2.10", + "@angular/cli": "^21.2.10", + "@angular/compiler-cli": "^21.2.12", + "typescript": "6.0.3" + } +} diff --git a/examples/angular/sub-components/public/favicon.ico b/examples/angular/sub-components/public/favicon.ico new file mode 100644 index 0000000000..57614f9c96 Binary files /dev/null and b/examples/angular/sub-components/public/favicon.ico differ diff --git a/examples/angular/sub-components/src/app/app.config.ts b/examples/angular/sub-components/src/app/app.config.ts new file mode 100644 index 0000000000..f997e614ac --- /dev/null +++ b/examples/angular/sub-components/src/app/app.config.ts @@ -0,0 +1,5 @@ +import type { ApplicationConfig } from '@angular/core' + +export const appConfig: ApplicationConfig = { + providers: [], +} diff --git a/examples/angular/sub-components/src/app/app.html b/examples/angular/sub-components/src/app/app.html new file mode 100644 index 0000000000..42e5af43d1 --- /dev/null +++ b/examples/angular/sub-components/src/app/app.html @@ -0,0 +1,57 @@ +
+ + +
+
+ + + @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) { + + @for (header of headerGroup.headers; track header.id) { + + } + + } + + + @for (row of table.getRowModel().rows; track row.id) { + + + @for (cell of row.getVisibleCells(); track cell.id) { + + } + + + @if (row.getIsExpanded()) { + + + + + } + } + +
+ @if (!header.isPlaceholder) { + +
+
+ } +
+ +
+
+
+ +
+
+
+ + +
+    
+      {{ row.original | json }}
+    
+  
+
diff --git a/examples/angular/sub-components/src/app/app.ts b/examples/angular/sub-components/src/app/app.ts new file mode 100644 index 0000000000..23f564c4b5 --- /dev/null +++ b/examples/angular/sub-components/src/app/app.ts @@ -0,0 +1,124 @@ +import { ChangeDetectionStrategy, Component, signal } from '@angular/core' +import { + FlexRender, + columnVisibilityFeature, + createExpandedRowModel, + flexRenderComponent, + injectTable, + rowExpandingFeature, + tableFeatures, +} from '@tanstack/angular-table' +import { ReactiveFormsModule } from '@angular/forms' +import { JsonPipe } from '@angular/common' +import { makeData } from './makeData' +import { ExpandableCell, ExpanderCell } from './expandable-cell' +import { SubComponent } from './sub-component/sub-component' +import type { Person } from './makeData' +import type { ColumnDef, ExpandedState } from '@tanstack/angular-table' + +const _features = tableFeatures({ + rowExpandingFeature, + columnVisibilityFeature, +}) + +const columns: Array> = [ + { + header: 'Name', + footer: (props) => props.column.id, + columns: [ + { + id: 'expander', + header: () => null, + cell: ({ row }) => { + if (!row.getCanExpand()) { + return '🔵' + } + return flexRenderComponent(ExpanderCell, { + inputs: { + expanded: row.getIsExpanded(), + }, + outputs: { + click: row.getToggleExpandedHandler(), + }, + }) + }, + }, + { + accessorKey: 'firstName', + header: 'First Name', + cell: () => flexRenderComponent(ExpandableCell), + footer: (props) => props.column.id, + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => info.getValue(), + header: () => 'Last Name', + footer: (props) => props.column.id, + }, + ], + }, + { + header: 'Info', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'age', + header: () => 'Age', + footer: (props) => props.column.id, + }, + { + header: 'More Info', + columns: [ + { + accessorKey: 'visits', + header: () => 'Visits', + footer: (props) => props.column.id, + }, + { + accessorKey: 'status', + header: 'Status', + footer: (props) => props.column.id, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + footer: (props) => props.column.id, + }, + ], + }, + ], + }, +] + +@Component({ + selector: 'app-root', + imports: [FlexRender, ReactiveFormsModule, JsonPipe, SubComponent], + templateUrl: './app.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class App { + readonly data = signal>(makeData(20)) + readonly expanded = signal({}) + + readonly table = injectTable(() => ({ + debugTable: true, + _features, + _rowModels: { + expandedRowModel: createExpandedRowModel(), + }, + data: this.data(), + columns, + state: { + expanded: this.expanded(), + }, + onExpandedChange: (updater) => + typeof updater === 'function' + ? this.expanded.update(updater) + : this.expanded.set(updater), + getRowCanExpand: () => true, + })) + + refreshData = () => this.data.set(makeData(20)) + stressTest = () => this.data.set(makeData(1_000)) +} diff --git a/examples/angular/sub-components/src/app/expandable-cell.ts b/examples/angular/sub-components/src/app/expandable-cell.ts new file mode 100644 index 0000000000..63f8d5bab7 --- /dev/null +++ b/examples/angular/sub-components/src/app/expandable-cell.ts @@ -0,0 +1,47 @@ +import { + ChangeDetectionStrategy, + Component, + input, + output, +} from '@angular/core' +import { injectFlexRenderContext } from '@tanstack/angular-table' +import type { CellContext, RowData } from '@tanstack/angular-table' + +@Component({ + standalone: true, + template: ` + + `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ExpanderCell { + readonly expanded = input.required() + + readonly click = output() +} + +@Component({ + standalone: true, + template: ` +
+ {{ context.getValue() }} +
+ `, + changeDetection: ChangeDetectionStrategy.OnPush, + styles: ` + :host { + > div { + padding-left: calc(2rem * var(--depth, 1)); + } + } + `, +}) +export class ExpandableCell { + readonly context = injectFlexRenderContext>() + + get row() { + return this.context.row + } +} diff --git a/examples/angular/sub-components/src/app/makeData.ts b/examples/angular/sub-components/src/app/makeData.ts new file mode 100644 index 0000000000..b9fb014aba --- /dev/null +++ b/examples/angular/sub-components/src/app/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/angular/sub-components/src/app/sub-component/sub-component.ts b/examples/angular/sub-components/src/app/sub-component/sub-component.ts new file mode 100644 index 0000000000..cedbd34ada --- /dev/null +++ b/examples/angular/sub-components/src/app/sub-component/sub-component.ts @@ -0,0 +1,18 @@ +import { Component, input } from '@angular/core' +import { JsonPipe } from '@angular/common' +import type { Row } from '@tanstack/angular-table' + +@Component({ + selector: 'app-sub', + template: ` +
+    
+      {{ row().original | json }}
+    
+  
+ `, + imports: [JsonPipe], +}) +export class SubComponent { + readonly row = input.required>() +} diff --git a/examples/angular/sub-components/src/index.html b/examples/angular/sub-components/src/index.html new file mode 100644 index 0000000000..d22422f6e5 --- /dev/null +++ b/examples/angular/sub-components/src/index.html @@ -0,0 +1,13 @@ + + + + + Sub componentss + + + + + + + + diff --git a/examples/angular/sub-components/src/main.ts b/examples/angular/sub-components/src/main.ts new file mode 100644 index 0000000000..8192dca694 --- /dev/null +++ b/examples/angular/sub-components/src/main.ts @@ -0,0 +1,5 @@ +import { bootstrapApplication } from '@angular/platform-browser' +import { appConfig } from './app/app.config' +import { App } from './app/app' + +bootstrapApplication(App, appConfig).catch((err) => console.error(err)) diff --git a/examples/angular/sub-components/src/styles.css b/examples/angular/sub-components/src/styles.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/angular/sub-components/src/styles.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/angular/sub-components/tsconfig.app.json b/examples/angular/sub-components/tsconfig.app.json new file mode 100644 index 0000000000..a0dcc37c60 --- /dev/null +++ b/examples/angular/sub-components/tsconfig.app.json @@ -0,0 +1,11 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] +} diff --git a/examples/angular/sub-components/tsconfig.json b/examples/angular/sub-components/tsconfig.json new file mode 100644 index 0000000000..32789cdc2b --- /dev/null +++ b/examples/angular/sub-components/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "isolatedModules": true, + "experimentalDecorators": true, + "importHelpers": true, + "target": "ES2022", + "module": "preserve" + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + }, + "include": ["**/*.ts", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/lit/basic-app-table/index.html b/examples/lit/basic-app-table/index.html new file mode 100644 index 0000000000..ea661c9253 --- /dev/null +++ b/examples/lit/basic-app-table/index.html @@ -0,0 +1,13 @@ + + + + + + TanStack Lit Table - Basic App Table + + +
+ + + + diff --git a/examples/lit/basic-app-table/package.json b/examples/lit/basic-app-table/package.json new file mode 100644 index 0000000000..3f0043a783 --- /dev/null +++ b/examples/lit/basic-app-table/package.json @@ -0,0 +1,22 @@ +{ + "name": "tanstack-lit-table-example-basic-app-table", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@lit/context": "^1.1.6", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "lit": "^3.3.2" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/lit/basic-app-table/src/main.ts b/examples/lit/basic-app-table/src/main.ts new file mode 100644 index 0000000000..c56f9470a2 --- /dev/null +++ b/examples/lit/basic-app-table/src/main.ts @@ -0,0 +1,439 @@ +import { customElement, state } from 'lit/decorators.js' +import { LitElement, html } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { + createSortedRowModel, + createTableHook, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/lit-table' +import { makeData } from './makeData' +import type { Person } from './makeData' + +// This example uses the new `createTableHook` method to create a re-usable table hook factory instead of independently using the standalone `TableController` and `createColumnHelper` method. You can choose to use either way. + +// 3. New in V9! Tell the table which features and row models we want to use. In this case, we want sorting. +const { useAppTable, createAppColumnHelper } = createTableHook({ + _features: tableFeatures({ + rowSortingFeature, + }), + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + }, + debugTable: true, +}) + +// 4. Create a helper object to help define our columns +const columnHelper = createAppColumnHelper() + +// 5. Define the columns for your table with a stable reference (in this case, defined statically outside of a Lit component) +const columns = columnHelper.columns([ + // accessorKey method (most common for simple use-cases) + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (info) => info.column.id, + }), + // accessorFn used (alternative) along with a custom id + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => html`${info.getValue()}`, + header: () => html`Last Name`, + footer: (info) => info.column.id, + }), + // accessorFn used to transform the data + columnHelper.accessor((row) => Number(row.age), { + id: 'age', + header: () => 'Age', + cell: (info) => info.renderValue(), + footer: (info) => info.column.id, + }), + columnHelper.accessor('visits', { + header: () => html`Visits`, + footer: (info) => info.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (info) => info.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (info) => info.column.id, + }), +]) + +@customElement('lit-table-example') +class LitTableExample extends LitElement { + @state() + private data: Array = makeData(20) + + // 6. Store data with a reactive reference + // (in Lit, we use @state() for reactive properties) + + // 7. Create the table instance with the required columns and data. + // Features and row models are already defined in the createTableHook call above + // NOTE: Capture `this` as `host` because inside the getter, `this` refers + // to the options object (not the LitElement), which would cause infinite recursion. + private appTable = (() => { + const host = this + return useAppTable( + this, + { + debugTable: true, + columns, + get data() { + return host.data + }, + }, + (state) => ({ sorting: state.sorting }), + ) + })() + + private rerender() { + this.data = makeData(20) + } + + // 8. Render your table markup from the table instance APIs + protected render(): unknown { + const table = this.appTable.table() + + return html` +
+
+ + +
+ + + ${repeat( + table.getHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` + + ${headerGroup.headers.map((header) => + table.AppHeader( + header, + (h) => html` + + `, + ), + )} + + `, + )} + + + ${table.getRowModel().rows.map( + (row) => html` + + ${row + .getAllCells() + .map((cell) => + table.AppCell( + cell, + (c) => html` `, + ), + )} + + `, + )} + + + ${repeat( + table.getFooterGroups(), + (footerGroup) => footerGroup.id, + (footerGroup) => html` + + ${footerGroup.headers.map((header) => + table.AppFooter( + header, + (h) => html` + + `, + ), + )} + + `, + )} + +
+ ${h.isPlaceholder + ? null + : html`
+ ${h.FlexRender()} + ${{ asc: ' \u{1F53C}', desc: ' \u{1F53D}' }[ + h.column.getIsSorted() as string + ] ?? null} +
`} +
${c.FlexRender()}
${h.isPlaceholder ? null : h.FlexRender()}
+
+ +
${JSON.stringify(table.state, null, 2)}
+
+ + ` + } +} diff --git a/examples/lit/basic-app-table/src/makeData.ts b/examples/lit/basic-app-table/src/makeData.ts new file mode 100644 index 0000000000..fd239ee360 --- /dev/null +++ b/examples/lit/basic-app-table/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + status: string + progress: number + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/lit/basic-app-table/tsconfig.json b/examples/lit/basic-app-table/tsconfig.json new file mode 100644 index 0000000000..02eb5e4c87 --- /dev/null +++ b/examples/lit/basic-app-table/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "emitDecoratorMetadata": true, + "noEmit": true, + "jsx": "react-jsx", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/lit/basic/vite.config.js b/examples/lit/basic-app-table/vite.config.js similarity index 100% rename from examples/lit/basic/vite.config.js rename to examples/lit/basic-app-table/vite.config.js diff --git a/examples/lit/basic-external-atoms/index.html b/examples/lit/basic-external-atoms/index.html new file mode 100644 index 0000000000..d9141c079c --- /dev/null +++ b/examples/lit/basic-external-atoms/index.html @@ -0,0 +1,13 @@ + + + + + + TanStack Lit Table - Basic External Store + + +
+ + + + diff --git a/examples/lit/basic-external-atoms/package.json b/examples/lit/basic-external-atoms/package.json new file mode 100644 index 0000000000..b21c464505 --- /dev/null +++ b/examples/lit/basic-external-atoms/package.json @@ -0,0 +1,22 @@ +{ + "name": "tanstack-lit-table-example-basic-external-atoms", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "@tanstack/store": "^0.11.0", + "lit": "^3.3.2" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/lit/basic-external-atoms/src/main.ts b/examples/lit/basic-external-atoms/src/main.ts new file mode 100644 index 0000000000..8890416c38 --- /dev/null +++ b/examples/lit/basic-external-atoms/src/main.ts @@ -0,0 +1,475 @@ +import { customElement, state } from 'lit/decorators.js' +import { LitElement, html } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { + FlexRender, + TableController, + createColumnHelper, + createPaginatedRowModel, + createSortedRowModel, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/lit-table' +import { createAtom } from '@tanstack/store' +import { makeData } from './makeData' +import type { PaginationState, SortingState } from '@tanstack/lit-table' +import type { Person } from './makeData' + +// This example demonstrates managing individual slices of table state via +// external TanStack Store atoms. Each atom is a stand-alone, subscribable +// reactive cell — you can read, write, or subscribe to it from anywhere, +// which makes it convenient for sharing state across components or modules. + +const _features = tableFeatures({ + rowPaginationFeature, + rowSortingFeature, +}) + +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('lastName', { + header: 'Last Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('age', { + header: 'Age', + }), + columnHelper.accessor('visits', { + header: 'Visits', + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + }), +]) + +// Create stable external atoms for the individual state slices you want to +// own. These live at module scope here, but could just as easily be created +// in a shared store module and imported by multiple components. +const sortingAtom = createAtom([]) +const paginationAtom = createAtom({ + pageIndex: 0, + pageSize: 10, +}) + +@customElement('lit-table-example') +class LitTableExample extends LitElement { + @state() + private _data: Array = makeData(1_000) + + private tableController = new TableController(this) + + protected render() { + // The table creates internal base atoms for every slice, and (because we + // pass `atoms` below) reads/writes for `sorting` and `pagination` are + // routed through the external atoms we created instead. The + // TableController subscribes to `table.store` for us, so any atom change + // flowing through the derived store triggers `host.requestUpdate()`. + const table = this.tableController.table( + { + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data: this._data, + atoms: { + sorting: sortingAtom, + pagination: paginationAtom, + }, + debugTable: true, + }, + (state) => ({ + sorting: state.sorting, + pagination: state.pagination, + }), + ) + + const pagination = paginationAtom.get() + const sorting = sortingAtom.get() + + return html` +
+
+ + +
+ + + ${repeat( + table.getHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` + + ${repeat( + headerGroup.headers, + (header) => header.id, + (header) => html` + + `, + )} + + `, + )} + + + ${repeat( + table.getRowModel().rows, + (row) => row.id, + (row) => html` + + ${repeat( + row.getAllCells(), + (cell) => cell.id, + (cell) => html` `, + )} + + `, + )} + +
+ ${header.isPlaceholder + ? null + : html`
+ ${FlexRender({ header })} + ${{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null} +
`} +
${FlexRender({ cell })}
+
+
+ + + + + +
Page
+ + ${(pagination.pageIndex + 1).toLocaleString()} of + ${table.getPageCount().toLocaleString()} + +
+ + | Go to page: + + + +
+
+
${JSON.stringify({ sorting, pagination }, null, 2)}
+
+ + ` + } +} diff --git a/examples/lit/basic-external-atoms/src/makeData.ts b/examples/lit/basic-external-atoms/src/makeData.ts new file mode 100644 index 0000000000..d274a17f45 --- /dev/null +++ b/examples/lit/basic-external-atoms/src/makeData.ts @@ -0,0 +1,47 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + return makeDataLevel() +} diff --git a/examples/lit/basic-external-atoms/tsconfig.json b/examples/lit/basic-external-atoms/tsconfig.json new file mode 100644 index 0000000000..02eb5e4c87 --- /dev/null +++ b/examples/lit/basic-external-atoms/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "emitDecoratorMetadata": true, + "noEmit": true, + "jsx": "react-jsx", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/lit/basic-external-atoms/vite.config.js b/examples/lit/basic-external-atoms/vite.config.js new file mode 100644 index 0000000000..75dafbd933 --- /dev/null +++ b/examples/lit/basic-external-atoms/vite.config.js @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite' +import rollupReplace from '@rollup/plugin-replace' + +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + ], +}) diff --git a/examples/lit/basic-external-state/index.html b/examples/lit/basic-external-state/index.html new file mode 100644 index 0000000000..12534a67c5 --- /dev/null +++ b/examples/lit/basic-external-state/index.html @@ -0,0 +1,13 @@ + + + + + + TanStack Lit Table - Basic External State + + +
+ + + + diff --git a/examples/lit/basic-external-state/package.json b/examples/lit/basic-external-state/package.json new file mode 100644 index 0000000000..e7763e0cf9 --- /dev/null +++ b/examples/lit/basic-external-state/package.json @@ -0,0 +1,21 @@ +{ + "name": "tanstack-lit-table-example-basic-external-state", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "lit": "^3.3.2" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/lit/basic-external-state/src/main.ts b/examples/lit/basic-external-state/src/main.ts new file mode 100644 index 0000000000..3dfcae7ef4 --- /dev/null +++ b/examples/lit/basic-external-state/src/main.ts @@ -0,0 +1,478 @@ +import { customElement, state } from 'lit/decorators.js' +import { LitElement, html } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { + FlexRender, + TableController, + createColumnHelper, + createPaginatedRowModel, + createSortedRowModel, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/lit-table' +import { makeData } from './makeData' +import type { PaginationState, SortingState } from '@tanstack/lit-table' +import type { Person } from './makeData' + +// This example demonstrates managing table state externally via Lit's @state() decorator instead of letting the table manage its own state internally. + +const _features = tableFeatures({ + rowPaginationFeature, + rowSortingFeature, +}) + +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('lastName', { + header: 'Last Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('age', { + header: 'Age', + }), + columnHelper.accessor('visits', { + header: 'Visits', + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + }), +]) + +@customElement('lit-table-example') +class LitTableExample extends LitElement { + @state() + private _data: Array = makeData(1_000) + + private tableController = new TableController(this) + + // Manage sorting state with Lit's @state() decorator + @state() + private sorting: SortingState = [] + + // Manage pagination state with Lit's @state() decorator + @state() + private pagination: PaginationState = { pageIndex: 0, pageSize: 10 } + + protected render() { + // Create the table and pass state + onChange handlers + const table = this.tableController.table( + { + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data: this._data, + state: { + sorting: this.sorting, // connect our sorting state back down to the table + pagination: this.pagination, // connect our pagination state back down to the table + }, + onSortingChange: (updater) => { + // raise sorting state changes to our own state management + this.sorting = + typeof updater === 'function' ? updater(this.sorting) : updater + }, + onPaginationChange: (updater) => { + // raise pagination state changes to our own state management + this.pagination = + typeof updater === 'function' ? updater(this.pagination) : updater + }, + }, + (state) => ({ + sorting: state.sorting, + pagination: state.pagination, + }), + ) + + return html` +
+
+ + +
+ + + ${repeat( + table.getHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` + + ${repeat( + headerGroup.headers, + (header) => header.id, + (header) => html` + + `, + )} + + `, + )} + + + ${repeat( + table.getRowModel().rows, + (row) => row.id, + (row) => html` + + ${repeat( + row.getAllCells(), + (cell) => cell.id, + (cell) => html` `, + )} + + `, + )} + +
+ ${header.isPlaceholder + ? null + : html`
+ ${FlexRender({ header })} + ${{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null} +
`} +
${FlexRender({ cell })}
+
+
+ + + + + +
Page
+ + ${(this.pagination.pageIndex + 1).toLocaleString()} of + ${table.getPageCount().toLocaleString()} + +
+ + | Go to page: + + + +
+
+
+${JSON.stringify(
+            { sorting: this.sorting, pagination: this.pagination },
+            null,
+            2,
+          )}
+
+ + ` + } +} diff --git a/examples/lit/basic-external-state/src/makeData.ts b/examples/lit/basic-external-state/src/makeData.ts new file mode 100644 index 0000000000..d274a17f45 --- /dev/null +++ b/examples/lit/basic-external-state/src/makeData.ts @@ -0,0 +1,47 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + return makeDataLevel() +} diff --git a/examples/lit/basic-external-state/tsconfig.json b/examples/lit/basic-external-state/tsconfig.json new file mode 100644 index 0000000000..02eb5e4c87 --- /dev/null +++ b/examples/lit/basic-external-state/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "emitDecoratorMetadata": true, + "noEmit": true, + "jsx": "react-jsx", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/lit/basic-external-state/vite.config.js b/examples/lit/basic-external-state/vite.config.js new file mode 100644 index 0000000000..75dafbd933 --- /dev/null +++ b/examples/lit/basic-external-state/vite.config.js @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite' +import rollupReplace from '@rollup/plugin-replace' + +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + ], +}) diff --git a/examples/lit/basic-table-controller/index.html b/examples/lit/basic-table-controller/index.html new file mode 100644 index 0000000000..5e1049b834 --- /dev/null +++ b/examples/lit/basic-table-controller/index.html @@ -0,0 +1,13 @@ + + + + + + TanStack Lit Table - Basic Table Controller + + +
+ + + + diff --git a/examples/lit/basic-table-controller/package.json b/examples/lit/basic-table-controller/package.json new file mode 100644 index 0000000000..3b22d59dbd --- /dev/null +++ b/examples/lit/basic-table-controller/package.json @@ -0,0 +1,21 @@ +{ + "name": "tanstack-lit-table-example-basic-table-controller", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "lit": "^3.3.2" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/lit/basic-table-controller/src/main.ts b/examples/lit/basic-table-controller/src/main.ts new file mode 100644 index 0000000000..a97bb6e31b --- /dev/null +++ b/examples/lit/basic-table-controller/src/main.ts @@ -0,0 +1,403 @@ +import { customElement, state } from 'lit/decorators.js' +import { LitElement, html } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { FlexRender, TableController, tableFeatures } from '@tanstack/lit-table' +import { makeData } from './makeData' +import type { ColumnDef } from '@tanstack/lit-table' +import type { Person } from './makeData' + +// This example uses the standalone `TableController` to create a table without the `createTableHook` util. + +// 3. New in V9! Tell the table which features and row models we want to use. In this case, this will be a basic table with no additional features +const _features = tableFeatures({}) // util method to create sharable TFeatures object/type + +// 4. Define the columns for your table. This uses the new `ColumnDef` type to define columns. Alternatively, check out the createTableHook/createAppColumnHelper util for an even more type-safe way to define columns. +const columns: Array> = [ + { + accessorKey: 'firstName', // accessorKey method (most common for simple use-cases) + header: 'First Name', + cell: (info) => info.getValue(), + footer: (info) => info.column.id, + }, + { + accessorFn: (row) => row.lastName, // accessorFn used (alternative) along with a custom id + id: 'lastName', + header: () => html`Last Name`, + cell: (info) => html`${info.getValue()}`, + footer: (info) => info.column.id, + }, + { + accessorFn: (row) => Number(row.age), // accessorFn used to transform the data + id: 'age', + header: () => 'Age', + cell: (info) => info.renderValue(), + footer: (info) => info.column.id, + }, + { + accessorKey: 'visits', + header: () => html`Visits`, + footer: (info) => info.column.id, + }, + { + accessorKey: 'status', + header: 'Status', + footer: (info) => info.column.id, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + footer: (info) => info.column.id, + }, +] + +@customElement('lit-table-example') +class LitTableExample extends LitElement { + // 5. Store data with a reactive reference + @state() + private data: Array = makeData(20) + + private tableController = new TableController(this) + + private rerender() { + this.data = makeData(20) + } + + protected render(): unknown { + const data = this.data + + // 6. Create the table instance with required _features, columns, and data + const table = this.tableController.table( + { + _features, // new required option in V9. Tell the table which features you are importing and using (better tree-shaking) + _rowModels: {}, // `Core` row model is now included by default, but you can still override it here + columns, + get data() { + return data + }, + // add additional table options here + }, + () => ({}), // selector - empty since we don't need any state + ) + + // 7. Render your table markup from the table instance APIs + return html` +
+
+ + +
+ + + ${repeat( + table.getHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` + + ${repeat( + headerGroup.headers, + (header) => header.id, + (header) => html` + + `, + )} + + `, + )} + + + ${repeat( + table.getRowModel().rows, + (row) => row.id, + (row) => html` + + ${repeat( + row.getAllCells(), + (cell) => cell.id, + (cell) => html` `, + )} + + `, + )} + + + ${repeat( + table.getFooterGroups(), + (footerGroup) => footerGroup.id, + (footerGroup) => html` + + ${repeat( + footerGroup.headers, + (header) => header.id, + (header) => html` + + `, + )} + + `, + )} + +
+ ${header.isPlaceholder ? null : FlexRender({ header })} +
${FlexRender({ cell })}
+ ${header.isPlaceholder + ? null + : FlexRender({ footer: header })} +
+
+ +
+ + ` + } +} diff --git a/examples/lit/basic-table-controller/src/makeData.ts b/examples/lit/basic-table-controller/src/makeData.ts new file mode 100644 index 0000000000..fd239ee360 --- /dev/null +++ b/examples/lit/basic-table-controller/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + status: string + progress: number + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/lit/basic-table-controller/tsconfig.json b/examples/lit/basic-table-controller/tsconfig.json new file mode 100644 index 0000000000..02eb5e4c87 --- /dev/null +++ b/examples/lit/basic-table-controller/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "emitDecoratorMetadata": true, + "noEmit": true, + "jsx": "react-jsx", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/lit/basic-table-controller/vite.config.js b/examples/lit/basic-table-controller/vite.config.js new file mode 100644 index 0000000000..fa3b238ac6 --- /dev/null +++ b/examples/lit/basic-table-controller/vite.config.js @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + ], +}) diff --git a/examples/lit/basic/index.html b/examples/lit/basic/index.html deleted file mode 100644 index 9807874fae..0000000000 --- a/examples/lit/basic/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - Vite App - - - -
- - - - diff --git a/examples/lit/basic/package.json b/examples/lit/basic/package.json deleted file mode 100644 index 37ee5419ca..0000000000 --- a/examples/lit/basic/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "tanstack-lit-table-example-basic", - "version": "0.0.0", - "private": true, - "scripts": { - "dev": "vite", - "build": "vite build", - "serve": "vite preview", - "start": "vite" - }, - "dependencies": { - "@tanstack/lit-table": "^8.20.5", - "@twind/core": "^1.1.3", - "@twind/preset-autoprefix": "^1.0.7", - "@twind/preset-tailwind": "^1.1.4", - "@twind/with-web-components": "^1.1.3", - "lit": "^3.1.4" - }, - "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "typescript": "5.4.5", - "vite": "^5.3.2" - } -} diff --git a/examples/lit/basic/src/main.ts b/examples/lit/basic/src/main.ts deleted file mode 100644 index e913a2f6d0..0000000000 --- a/examples/lit/basic/src/main.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { customElement } from 'lit/decorators.js' -import { html, LitElement } from 'lit' -import { repeat } from 'lit/directives/repeat.js' -import { - createColumnHelper, - flexRender, - getCoreRowModel, - TableController, -} from '@tanstack/lit-table' -import install from '@twind/with-web-components' -import config from '../twind.config' - -const withTwind = install(config) - -type Person = { - firstName: string - lastName: string - age: number - visits: number - status: string - progress: number -} - -const columnHelper = createColumnHelper() - -const columns = [ - columnHelper.accessor('firstName', { - cell: info => info.getValue(), - footer: info => info.column.id, - }), - columnHelper.accessor(row => row.lastName, { - id: 'lastName', - cell: info => html`${info.getValue()}`, - header: () => html`Last Name`, - footer: info => info.column.id, - }), - columnHelper.accessor('age', { - header: () => 'Age', - cell: info => info.renderValue(), - footer: info => info.column.id, - }), - columnHelper.accessor('visits', { - header: () => html`Visits`, - footer: info => info.column.id, - }), - columnHelper.accessor('status', { - header: 'Status', - footer: info => info.column.id, - }), - columnHelper.accessor('progress', { - header: 'Profile Progress', - footer: info => info.column.id, - }), -] - -const data: Person[] = [ - { - firstName: 'tanner', - lastName: 'linsley', - age: 24, - visits: 100, - status: 'In Relationship', - progress: 50, - }, - { - firstName: 'tandy', - lastName: 'miller', - age: 40, - visits: 40, - status: 'Single', - progress: 80, - }, - { - firstName: 'joe', - lastName: 'dirte', - age: 45, - visits: 20, - status: 'Complicated', - progress: 10, - }, - { - firstName: 'mor', - lastName: 'kadosh', - age: 31, - visits: 30, - status: 'In Relationship', - progress: 90, - }, -] - -@customElement('lit-table-example') -@withTwind -class LitTableExample extends LitElement { - private tableController = new TableController(this) - - protected render(): unknown { - const table = this.tableController.table({ - columns, - data, - getCoreRowModel: getCoreRowModel(), - }) - - return html` - - - ${repeat( - table.getHeaderGroups(), - headerGroup => headerGroup.id, - headerGroup => - html`${repeat( - headerGroup.headers, - header => header.id, - header => - html` ` - )}` - )} - - - ${repeat( - table.getRowModel().rows, - row => row.id, - row => html` - - ${repeat( - row.getVisibleCells(), - cell => cell.id, - cell => - html` ` - )} - - ` - )} - - - ${repeat( - table.getFooterGroups(), - footerGroup => footerGroup.id, - footerGroup => html` - - ${repeat( - footerGroup.headers, - header => header.id, - header => html` - - ` - )} - - ` - )} - -
- ${header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} -
- ${flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} -
- ${header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.footer, - header.getContext() - )} -
- - ` - } -} diff --git a/examples/lit/basic/tsconfig.json b/examples/lit/basic/tsconfig.json deleted file mode 100644 index 3141563c8a..0000000000 --- a/examples/lit/basic/tsconfig.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "emitDecoratorMetadata": true, - "noEmit": true, - "jsx": "react-jsx", - "experimentalDecorators": true, - "useDefineForClassFields": false, - - /* Linting */ - "strict": true, - "noUnusedLocals": false, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"] -} diff --git a/examples/lit/basic/twind.config.ts b/examples/lit/basic/twind.config.ts deleted file mode 100644 index f7e85ff4ae..0000000000 --- a/examples/lit/basic/twind.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from '@twind/core' -import presetAutoprefix from '@twind/preset-autoprefix' -import presetTailwind from '@twind/preset-tailwind/base' - -export default defineConfig({ - presets: [presetAutoprefix(), presetTailwind()], -}) diff --git a/examples/lit/column-groups/index.html b/examples/lit/column-groups/index.html new file mode 100644 index 0000000000..15b0437b92 --- /dev/null +++ b/examples/lit/column-groups/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + +
+ + + + diff --git a/examples/lit/column-groups/package.json b/examples/lit/column-groups/package.json new file mode 100644 index 0000000000..de39aa1f81 --- /dev/null +++ b/examples/lit/column-groups/package.json @@ -0,0 +1,21 @@ +{ + "name": "tanstack-lit-table-example-column-groups", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "lit": "^3.3.2" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/lit/column-groups/src/main.ts b/examples/lit/column-groups/src/main.ts new file mode 100644 index 0000000000..a2384c076e --- /dev/null +++ b/examples/lit/column-groups/src/main.ts @@ -0,0 +1,392 @@ +import { customElement, state } from 'lit/decorators.js' +import { LitElement, html } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { FlexRender, TableController, tableFeatures } from '@tanstack/lit-table' +import { makeData } from './makeData' +import type { ColumnDef } from '@tanstack/lit-table' +import type { Person } from './makeData' + +const _features = tableFeatures({}) + +const defaultColumns: Array> = [ + { + header: 'Name', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'firstName', + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => info.getValue(), + header: () => html`Last Name`, + footer: (props) => props.column.id, + }, + ], + }, + { + header: 'Info', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'age', + header: () => 'Age', + footer: (props) => props.column.id, + }, + { + header: 'More Info', + columns: [ + { + accessorKey: 'visits', + header: () => html`Visits`, + footer: (props) => props.column.id, + }, + { + accessorKey: 'status', + header: 'Status', + footer: (props) => props.column.id, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + footer: (props) => props.column.id, + }, + ], + }, + ], + }, +] + +@customElement('lit-table-example') +class LitTableExample extends LitElement { + @state() + private _data: Array = makeData(20) + + private tableController = new TableController(this) + + protected render() { + const table = this.tableController.table( + { + _features, + _rowModels: {}, + columns: defaultColumns, + data: this._data, + }, + () => ({}), + ) + + return html` +
+
+ + +
+ + + ${repeat( + table.getHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` + + ${repeat( + headerGroup.headers, + (header) => header.id, + (header) => html` + + `, + )} + + `, + )} + + + ${repeat( + table.getRowModel().rows, + (row) => row.id, + (row) => html` + + ${repeat( + row.getAllCells(), + (cell) => cell.id, + (cell) => html` `, + )} + + `, + )} + + + ${repeat( + table.getFooterGroups(), + (footerGroup) => footerGroup.id, + (footerGroup) => html` + + ${repeat( + footerGroup.headers, + (header) => header.id, + (header) => html` + + `, + )} + + `, + )} + +
+ ${header.isPlaceholder ? null : FlexRender({ header })} +
${FlexRender({ cell })}
+ ${header.isPlaceholder + ? null + : FlexRender({ footer: header })} +
+
+ + ` + } +} diff --git a/examples/lit/column-groups/src/makeData.ts b/examples/lit/column-groups/src/makeData.ts new file mode 100644 index 0000000000..d63c724b74 --- /dev/null +++ b/examples/lit/column-groups/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/lit/column-groups/tsconfig.json b/examples/lit/column-groups/tsconfig.json new file mode 100644 index 0000000000..02eb5e4c87 --- /dev/null +++ b/examples/lit/column-groups/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "emitDecoratorMetadata": true, + "noEmit": true, + "jsx": "react-jsx", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/lit/column-groups/vite.config.js b/examples/lit/column-groups/vite.config.js new file mode 100644 index 0000000000..0e960eede3 --- /dev/null +++ b/examples/lit/column-groups/vite.config.js @@ -0,0 +1,13 @@ +import { defineConfig } from 'vite' +import rollupReplace from '@rollup/plugin-replace' +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + ], +}) diff --git a/examples/lit/column-ordering/index.html b/examples/lit/column-ordering/index.html new file mode 100644 index 0000000000..15b0437b92 --- /dev/null +++ b/examples/lit/column-ordering/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + +
+ + + + diff --git a/examples/lit/column-ordering/package.json b/examples/lit/column-ordering/package.json new file mode 100644 index 0000000000..5a3e738f47 --- /dev/null +++ b/examples/lit/column-ordering/package.json @@ -0,0 +1,21 @@ +{ + "name": "tanstack-lit-table-example-column-ordering", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "lit": "^3.3.2" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/lit/column-ordering/src/main.ts b/examples/lit/column-ordering/src/main.ts new file mode 100644 index 0000000000..2cadeb1bec --- /dev/null +++ b/examples/lit/column-ordering/src/main.ts @@ -0,0 +1,479 @@ +import { customElement, state } from 'lit/decorators.js' +import { LitElement, html } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { faker } from '@faker-js/faker' +import { + FlexRender, + TableController, + columnOrderingFeature, + columnVisibilityFeature, + tableFeatures, +} from '@tanstack/lit-table' +import { makeData } from './makeData' +import type { + ColumnDef, + ColumnOrderState, + ColumnVisibilityState, +} from '@tanstack/lit-table' +import type { Person } from './makeData' + +const _features = tableFeatures({ + columnOrderingFeature, + columnVisibilityFeature, +}) + +const defaultColumns: Array> = [ + { + header: 'Name', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'firstName', + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => info.getValue(), + header: () => html`Last Name`, + footer: (props) => props.column.id, + }, + ], + }, + { + header: 'Info', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'age', + header: () => 'Age', + footer: (props) => props.column.id, + }, + { + header: 'More Info', + columns: [ + { + accessorKey: 'visits', + header: () => html`Visits`, + footer: (props) => props.column.id, + }, + { + accessorKey: 'status', + header: 'Status', + footer: (props) => props.column.id, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + footer: (props) => props.column.id, + }, + ], + }, + ], + }, +] + +@customElement('lit-table-example') +class LitTableExample extends LitElement { + private tableController = new TableController(this) + + @state() + private _data: Array = makeData(20) + + @state() + private columnOrder: ColumnOrderState = [] + + @state() + private columnVisibility: ColumnVisibilityState = {} + + protected render() { + const table = this.tableController.table( + { + _features, + _rowModels: {}, + columns: defaultColumns, + data: this._data, + state: { + columnOrder: this.columnOrder, + columnVisibility: this.columnVisibility, + }, + onColumnOrderChange: (updaterOrValue) => { + if (typeof updaterOrValue === 'function') { + this.columnOrder = updaterOrValue(this.columnOrder) + } else { + this.columnOrder = updaterOrValue + } + }, + onColumnVisibilityChange: (updaterOrValue) => { + if (typeof updaterOrValue === 'function') { + this.columnVisibility = updaterOrValue(this.columnVisibility) + } else { + this.columnVisibility = updaterOrValue + } + }, + }, + (state) => ({ + columnOrder: state.columnOrder, + columnVisibility: state.columnVisibility, + }), + ) + + const randomizeColumns = () => { + table.setColumnOrder( + faker.helpers.shuffle(table.getAllLeafColumns().map((d) => d.id)), + ) + } + + return html` +
+
+ + +
+
+
+
+ +
+ ${table.getAllLeafColumns().map( + (column) => html` +
+ +
+ `, + )} +
+
+
+ + +
+
+ + + ${table.getHeaderGroups().map( + (headerGroup) => html` + + ${headerGroup.headers.map( + (header) => html` + + `, + )} + + `, + )} + + + ${repeat( + table.getRowModel().rows, + (row) => row.id, + (row) => html` + + ${repeat( + row.getVisibleCells(), + (cell) => cell.id, + (cell) => html` `, + )} + + `, + )} + + + ${table.getFooterGroups().map( + (footerGroup) => html` + + ${footerGroup.headers.map( + (header) => html` + + `, + )} + + `, + )} + +
+ ${header.isPlaceholder ? null : FlexRender({ header })} +
${FlexRender({ cell })}
+ ${header.isPlaceholder + ? null + : FlexRender({ footer: header })} +
+
+
${JSON.stringify(table.state, null, 2)}
+
+ + ` + } +} diff --git a/examples/lit/column-ordering/src/makeData.ts b/examples/lit/column-ordering/src/makeData.ts new file mode 100644 index 0000000000..d63c724b74 --- /dev/null +++ b/examples/lit/column-ordering/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/lit/column-ordering/tsconfig.json b/examples/lit/column-ordering/tsconfig.json new file mode 100644 index 0000000000..02eb5e4c87 --- /dev/null +++ b/examples/lit/column-ordering/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "emitDecoratorMetadata": true, + "noEmit": true, + "jsx": "react-jsx", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/lit/column-ordering/vite.config.js b/examples/lit/column-ordering/vite.config.js new file mode 100644 index 0000000000..0e960eede3 --- /dev/null +++ b/examples/lit/column-ordering/vite.config.js @@ -0,0 +1,13 @@ +import { defineConfig } from 'vite' +import rollupReplace from '@rollup/plugin-replace' +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + ], +}) diff --git a/examples/lit/column-pinning-split/index.html b/examples/lit/column-pinning-split/index.html new file mode 100644 index 0000000000..767af8277e --- /dev/null +++ b/examples/lit/column-pinning-split/index.html @@ -0,0 +1,13 @@ + + + + + + TanStack Lit Table - Column Pinning Split + + +
+ + + + diff --git a/examples/lit/column-pinning-split/package.json b/examples/lit/column-pinning-split/package.json new file mode 100644 index 0000000000..9831e1bedc --- /dev/null +++ b/examples/lit/column-pinning-split/package.json @@ -0,0 +1,21 @@ +{ + "name": "tanstack-lit-table-example-column-pinning-split", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "lit": "^3.3.2" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/lit/column-pinning-split/src/main.ts b/examples/lit/column-pinning-split/src/main.ts new file mode 100644 index 0000000000..ee6261783e --- /dev/null +++ b/examples/lit/column-pinning-split/src/main.ts @@ -0,0 +1,604 @@ +import { customElement, state } from 'lit/decorators.js' +import { LitElement, html } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { faker } from '@faker-js/faker' +import { + FlexRender, + TableController, + columnOrderingFeature, + columnPinningFeature, + columnVisibilityFeature, + tableFeatures, +} from '@tanstack/lit-table' +import { makeData } from './makeData' +import type { + ColumnDef, + ColumnOrderState, + ColumnPinningState, + ColumnVisibilityState, +} from '@tanstack/lit-table' +import type { Person } from './makeData' + +const _features = tableFeatures({ + columnOrderingFeature, + columnPinningFeature, + columnVisibilityFeature, +}) + +const defaultColumns: Array> = [ + { + header: 'Name', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'firstName', + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => info.getValue(), + header: () => html`Last Name`, + footer: (props) => props.column.id, + }, + ], + }, + { + header: 'Info', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'age', + header: () => 'Age', + footer: (props) => props.column.id, + }, + { + header: 'More Info', + columns: [ + { + accessorKey: 'visits', + header: () => html`Visits`, + footer: (props) => props.column.id, + }, + { + accessorKey: 'status', + header: 'Status', + footer: (props) => props.column.id, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + footer: (props) => props.column.id, + }, + ], + }, + ], + }, +] + +@customElement('lit-table-example') +class LitTableExample extends LitElement { + private tableController = new TableController(this) + + @state() + private _data: Array = makeData(1_000) + + @state() + private columnOrder: ColumnOrderState = [] + + @state() + private columnVisibility: ColumnVisibilityState = {} + + @state() + private columnPinning: ColumnPinningState = { left: [], right: [] } + + protected render() { + const table = this.tableController.table( + { + _features, + _rowModels: {}, + columns: defaultColumns, + data: this._data, + state: { + columnOrder: this.columnOrder, + columnVisibility: this.columnVisibility, + columnPinning: this.columnPinning, + }, + onColumnOrderChange: (updaterOrValue) => { + if (typeof updaterOrValue === 'function') { + this.columnOrder = updaterOrValue(this.columnOrder) + } else { + this.columnOrder = updaterOrValue + } + }, + onColumnVisibilityChange: (updaterOrValue) => { + if (typeof updaterOrValue === 'function') { + this.columnVisibility = updaterOrValue(this.columnVisibility) + } else { + this.columnVisibility = updaterOrValue + } + }, + onColumnPinningChange: (updaterOrValue) => { + if (typeof updaterOrValue === 'function') { + this.columnPinning = updaterOrValue(this.columnPinning) + } else { + this.columnPinning = updaterOrValue + } + }, + debugTable: true, + debugHeaders: true, + debugColumns: true, + }, + (state) => ({ + columnOrder: state.columnOrder, + columnVisibility: state.columnVisibility, + columnPinning: state.columnPinning, + }), + ) + + const randomizeColumns = () => { + table.setColumnOrder( + faker.helpers.shuffle(table.getAllLeafColumns().map((d) => d.id)), + ) + } + + const renderPinButtons = ( + column: ReturnType[0], + ) => html` +
+ ${column.getIsPinned() !== 'left' + ? html` + + ` + : null} + ${column.getIsPinned() + ? html` + + ` + : null} + ${column.getIsPinned() !== 'right' + ? html` + + ` + : null} +
+ ` + + return html` +
+
+
+ +
+ ${table.getAllLeafColumns().map( + (column) => html` +
+ +
+ `, + )} +
+
+
+ + + +
+
+

+ This example takes advantage of the "splitting" APIs. (APIs that have + "left", "center", and "right" modifiers) +

+
+ + + + ${repeat( + table.getLeftHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` + + ${repeat( + headerGroup.headers, + (header) => header.id, + (header) => html` + + `, + )} + + `, + )} + + + ${repeat( + table.getRowModel().rows.slice(0, 20), + (row) => row.id, + (row) => html` + + ${repeat( + row.getLeftVisibleCells(), + (cell) => cell.id, + (cell) => html` `, + )} + + `, + )} + +
+
+ ${header.isPlaceholder + ? null + : FlexRender({ header })} +
+ ${!header.isPlaceholder && header.column.getCanPin() + ? renderPinButtons(header.column) + : null} +
${FlexRender({ cell })}
+ + + + ${repeat( + table.getCenterHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` + + ${repeat( + headerGroup.headers, + (header) => header.id, + (header) => html` + + `, + )} + + `, + )} + + + ${repeat( + table.getRowModel().rows.slice(0, 20), + (row) => row.id, + (row) => html` + + ${repeat( + row.getCenterVisibleCells(), + (cell) => cell.id, + (cell) => html` `, + )} + + `, + )} + +
+
+ ${header.isPlaceholder + ? null + : FlexRender({ header })} +
+ ${!header.isPlaceholder && header.column.getCanPin() + ? renderPinButtons(header.column) + : null} +
${FlexRender({ cell })}
+ + + + ${repeat( + table.getRightHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` + + ${repeat( + headerGroup.headers, + (header) => header.id, + (header) => html` + + `, + )} + + `, + )} + + + ${repeat( + table.getRowModel().rows.slice(0, 20), + (row) => row.id, + (row) => html` + + ${repeat( + row.getRightVisibleCells(), + (cell) => cell.id, + (cell) => html` `, + )} + + `, + )} + +
+
+ ${header.isPlaceholder + ? null + : FlexRender({ header })} +
+ ${!header.isPlaceholder && header.column.getCanPin() + ? renderPinButtons(header.column) + : null} +
${FlexRender({ cell })}
+
+
+
${JSON.stringify(table.state, null, 2)}
+
+ + ` + } +} diff --git a/examples/lit/column-pinning-split/src/makeData.ts b/examples/lit/column-pinning-split/src/makeData.ts new file mode 100644 index 0000000000..d63c724b74 --- /dev/null +++ b/examples/lit/column-pinning-split/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/lit/column-pinning-split/tsconfig.json b/examples/lit/column-pinning-split/tsconfig.json new file mode 100644 index 0000000000..02eb5e4c87 --- /dev/null +++ b/examples/lit/column-pinning-split/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "emitDecoratorMetadata": true, + "noEmit": true, + "jsx": "react-jsx", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/lit/column-pinning-split/vite.config.js b/examples/lit/column-pinning-split/vite.config.js new file mode 100644 index 0000000000..0e960eede3 --- /dev/null +++ b/examples/lit/column-pinning-split/vite.config.js @@ -0,0 +1,13 @@ +import { defineConfig } from 'vite' +import rollupReplace from '@rollup/plugin-replace' +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + ], +}) diff --git a/examples/lit/column-pinning-sticky/index.html b/examples/lit/column-pinning-sticky/index.html new file mode 100644 index 0000000000..3eea6dd8d5 --- /dev/null +++ b/examples/lit/column-pinning-sticky/index.html @@ -0,0 +1,13 @@ + + + + + + TanStack Lit Table - Column Pinning Sticky + + +
+ + + + diff --git a/examples/lit/column-pinning-sticky/package.json b/examples/lit/column-pinning-sticky/package.json new file mode 100644 index 0000000000..368d09111b --- /dev/null +++ b/examples/lit/column-pinning-sticky/package.json @@ -0,0 +1,21 @@ +{ + "name": "tanstack-lit-table-example-column-pinning-sticky", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "lit": "^3.3.2" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/lit/column-pinning-sticky/src/main.ts b/examples/lit/column-pinning-sticky/src/main.ts new file mode 100644 index 0000000000..298fc83bf9 --- /dev/null +++ b/examples/lit/column-pinning-sticky/src/main.ts @@ -0,0 +1,593 @@ +import { customElement, state } from 'lit/decorators.js' +import { LitElement, html } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { styleMap } from 'lit/directives/style-map.js' +import { faker } from '@faker-js/faker' +import { + FlexRender, + TableController, + columnOrderingFeature, + columnPinningFeature, + columnResizingFeature, + columnSizingFeature, + columnVisibilityFeature, + tableFeatures, +} from '@tanstack/lit-table' +import { makeData } from './makeData' +import type { + Column, + ColumnDef, + ColumnPinningState, + ColumnSizingState, + ColumnVisibilityState, +} from '@tanstack/lit-table' +import type { Person } from './makeData' + +const _features = tableFeatures({ + columnOrderingFeature, + columnPinningFeature, + columnResizingFeature, + columnSizingFeature, + columnVisibilityFeature, +}) + +const getCommonPinningStyles = ( + column: Column, +): Record => { + const isPinned = column.getIsPinned() + const isLastLeftPinnedColumn = + isPinned === 'left' && column.getIsLastColumn('left') + const isFirstRightPinnedColumn = + isPinned === 'right' && column.getIsFirstColumn('right') + + return { + 'box-shadow': isLastLeftPinnedColumn + ? '-4px 0 4px -4px gray inset' + : isFirstRightPinnedColumn + ? '4px 0 4px -4px gray inset' + : undefined, + left: isPinned === 'left' ? `${column.getStart('left')}px` : undefined, + right: isPinned === 'right' ? `${column.getAfter('right')}px` : undefined, + opacity: isPinned ? '0.95' : '1', + position: isPinned ? 'sticky' : 'relative', + width: `${column.getSize()}px`, + 'z-index': isPinned ? '1' : '0', + } +} + +const defaultColumns: Array> = [ + { + accessorKey: 'firstName', + id: 'firstName', + header: 'First Name', + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + size: 180, + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => info.getValue(), + header: () => html`Last Name`, + footer: (props) => props.column.id, + size: 180, + }, + { + accessorKey: 'age', + id: 'age', + header: 'Age', + footer: (props) => props.column.id, + size: 180, + }, + { + accessorKey: 'visits', + id: 'visits', + header: 'Visits', + footer: (props) => props.column.id, + size: 180, + }, + { + accessorKey: 'status', + id: 'status', + header: 'Status', + footer: (props) => props.column.id, + size: 180, + }, + { + accessorKey: 'progress', + id: 'progress', + header: 'Profile Progress', + footer: (props) => props.column.id, + size: 180, + }, +] + +@customElement('lit-table-example') +class LitTableExample extends LitElement { + private tableController = new TableController(this) + + @state() + private _data: Array = makeData(20) + + @state() + private columnVisibility: ColumnVisibilityState = {} + + @state() + private columnPinning: ColumnPinningState = { left: [], right: [] } + + @state() + private columnSizing: ColumnSizingState = {} + + protected render() { + const table = this.tableController.table( + { + _features, + _rowModels: {}, + columns: defaultColumns, + data: this._data, + state: { + columnVisibility: this.columnVisibility, + columnPinning: this.columnPinning, + columnSizing: this.columnSizing, + }, + onColumnVisibilityChange: (updaterOrValue) => { + if (typeof updaterOrValue === 'function') { + this.columnVisibility = updaterOrValue(this.columnVisibility) + } else { + this.columnVisibility = updaterOrValue + } + }, + onColumnPinningChange: (updaterOrValue) => { + if (typeof updaterOrValue === 'function') { + this.columnPinning = updaterOrValue(this.columnPinning) + } else { + this.columnPinning = updaterOrValue + } + }, + onColumnSizingChange: (updaterOrValue) => { + if (typeof updaterOrValue === 'function') { + this.columnSizing = updaterOrValue(this.columnSizing) + } else { + this.columnSizing = updaterOrValue + } + }, + columnResizeMode: 'onChange', + debugTable: true, + debugHeaders: true, + debugColumns: true, + }, + (state) => ({ + columnPinning: state.columnPinning, + columnSizing: state.columnSizing, + }), + ) + + const randomizeColumns = () => { + table.setColumnOrder( + faker.helpers.shuffle(table.getAllLeafColumns().map((d) => d.id)), + ) + } + + return html` +
+
+
+ +
+ ${table.getAllLeafColumns().map( + (column) => html` +
+ +
+ `, + )} +
+
+
+ + + +
+
+
+ + + ${repeat( + table.getHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` + + ${repeat( + headerGroup.headers, + (header) => header.id, + (header) => { + const { column } = header + return html` + + ` + }, + )} + + `, + )} + + + ${repeat( + table.getRowModel().rows, + (row) => row.id, + (row) => html` + + ${repeat( + row.getVisibleCells(), + (cell) => cell.id, + (cell) => { + const { column } = cell + return html` + + ` + }, + )} + + `, + )} + +
+
+ ${header.isPlaceholder + ? null + : html`${FlexRender({ header })} `} + ${column.getIndex( + column.getIsPinned() || 'center', + )} +
+ ${!header.isPlaceholder && header.column.getCanPin() + ? html` +
+ ${header.column.getIsPinned() !== 'left' + ? html` + + ` + : null} + ${header.column.getIsPinned() + ? html` + + ` + : null} + ${header.column.getIsPinned() !== 'right' + ? html` + + ` + : null} +
+ ` + : null} +
+
+ ${FlexRender({ cell })} +
+
+
+
${JSON.stringify(table.state, null, 2)}
+
+ + ` + } +} diff --git a/examples/lit/column-pinning-sticky/src/makeData.ts b/examples/lit/column-pinning-sticky/src/makeData.ts new file mode 100644 index 0000000000..d63c724b74 --- /dev/null +++ b/examples/lit/column-pinning-sticky/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/lit/column-pinning-sticky/tsconfig.json b/examples/lit/column-pinning-sticky/tsconfig.json new file mode 100644 index 0000000000..02eb5e4c87 --- /dev/null +++ b/examples/lit/column-pinning-sticky/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "emitDecoratorMetadata": true, + "noEmit": true, + "jsx": "react-jsx", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/lit/column-pinning-sticky/vite.config.js b/examples/lit/column-pinning-sticky/vite.config.js new file mode 100644 index 0000000000..0e960eede3 --- /dev/null +++ b/examples/lit/column-pinning-sticky/vite.config.js @@ -0,0 +1,13 @@ +import { defineConfig } from 'vite' +import rollupReplace from '@rollup/plugin-replace' +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + ], +}) diff --git a/examples/lit/column-pinning/index.html b/examples/lit/column-pinning/index.html new file mode 100644 index 0000000000..8d1e1a2e4f --- /dev/null +++ b/examples/lit/column-pinning/index.html @@ -0,0 +1,13 @@ + + + + + + TanStack Lit Table - Column Pinning + + +
+ + + + diff --git a/examples/lit/column-pinning/package.json b/examples/lit/column-pinning/package.json new file mode 100644 index 0000000000..0a18317bca --- /dev/null +++ b/examples/lit/column-pinning/package.json @@ -0,0 +1,21 @@ +{ + "name": "tanstack-lit-table-example-column-pinning", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "lit": "^3.3.2" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/lit/column-pinning/src/main.ts b/examples/lit/column-pinning/src/main.ts new file mode 100644 index 0000000000..4a1b4c5f4b --- /dev/null +++ b/examples/lit/column-pinning/src/main.ts @@ -0,0 +1,523 @@ +import { customElement, state } from 'lit/decorators.js' +import { LitElement, html } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { faker } from '@faker-js/faker' +import { + FlexRender, + TableController, + columnOrderingFeature, + columnPinningFeature, + columnVisibilityFeature, + tableFeatures, +} from '@tanstack/lit-table' +import { makeData } from './makeData' +import type { + ColumnDef, + ColumnOrderState, + ColumnPinningState, + ColumnVisibilityState, +} from '@tanstack/lit-table' +import type { Person } from './makeData' + +const _features = tableFeatures({ + columnOrderingFeature, + columnPinningFeature, + columnVisibilityFeature, +}) + +const defaultColumns: Array> = [ + { + header: 'Name', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'firstName', + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => info.getValue(), + header: () => html`Last Name`, + footer: (props) => props.column.id, + }, + ], + }, + { + header: 'Info', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'age', + header: () => 'Age', + footer: (props) => props.column.id, + }, + { + header: 'More Info', + columns: [ + { + accessorKey: 'visits', + header: () => html`Visits`, + footer: (props) => props.column.id, + }, + { + accessorKey: 'status', + header: 'Status', + footer: (props) => props.column.id, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + footer: (props) => props.column.id, + }, + ], + }, + ], + }, +] + +@customElement('lit-table-example') +class LitTableExample extends LitElement { + private tableController = new TableController(this) + + @state() + private _data: Array = makeData(1_000) + + @state() + private columnOrder: ColumnOrderState = [] + + @state() + private columnVisibility: ColumnVisibilityState = {} + + @state() + private columnPinning: ColumnPinningState = { left: [], right: [] } + + protected render() { + const table = this.tableController.table( + { + _features, + _rowModels: {}, + columns: defaultColumns, + data: this._data, + state: { + columnOrder: this.columnOrder, + columnVisibility: this.columnVisibility, + columnPinning: this.columnPinning, + }, + onColumnOrderChange: (updaterOrValue) => { + if (typeof updaterOrValue === 'function') { + this.columnOrder = updaterOrValue(this.columnOrder) + } else { + this.columnOrder = updaterOrValue + } + }, + onColumnVisibilityChange: (updaterOrValue) => { + if (typeof updaterOrValue === 'function') { + this.columnVisibility = updaterOrValue(this.columnVisibility) + } else { + this.columnVisibility = updaterOrValue + } + }, + onColumnPinningChange: (updaterOrValue) => { + if (typeof updaterOrValue === 'function') { + this.columnPinning = updaterOrValue(this.columnPinning) + } else { + this.columnPinning = updaterOrValue + } + }, + debugTable: true, + debugHeaders: true, + debugColumns: true, + }, + (state) => ({ + columnOrder: state.columnOrder, + columnVisibility: state.columnVisibility, + columnPinning: state.columnPinning, + }), + ) + + const randomizeColumns = () => { + table.setColumnOrder( + faker.helpers.shuffle(table.getAllLeafColumns().map((d) => d.id)), + ) + } + + return html` +
+
+
+ +
+ ${table.getAllLeafColumns().map( + (column) => html` +
+ +
+ `, + )} +
+
+
+ + + +
+
+

+ This example uses the non-split APIs. Columns are just reordered + within 1 table instead of being split into 3 different tables. +

+
+ + + ${repeat( + table.getHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` + + ${repeat( + headerGroup.headers, + (header) => header.id, + (header) => html` + + `, + )} + + `, + )} + + + ${repeat( + table.getRowModel().rows.slice(0, 20), + (row) => row.id, + (row) => html` + + ${repeat( + row.getVisibleCells(), + (cell) => cell.id, + (cell) => html` `, + )} + + `, + )} + +
+
+ ${header.isPlaceholder + ? null + : FlexRender({ header })} +
+ ${!header.isPlaceholder && header.column.getCanPin() + ? html` +
+ ${header.column.getIsPinned() !== 'left' + ? html` + + ` + : null} + ${header.column.getIsPinned() + ? html` + + ` + : null} + ${header.column.getIsPinned() !== 'right' + ? html` + + ` + : null} +
+ ` + : null} +
${FlexRender({ cell })}
+
+
+
${JSON.stringify(table.state, null, 2)}
+
+ + ` + } +} diff --git a/examples/lit/column-pinning/src/makeData.ts b/examples/lit/column-pinning/src/makeData.ts new file mode 100644 index 0000000000..d63c724b74 --- /dev/null +++ b/examples/lit/column-pinning/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/lit/column-pinning/tsconfig.json b/examples/lit/column-pinning/tsconfig.json new file mode 100644 index 0000000000..02eb5e4c87 --- /dev/null +++ b/examples/lit/column-pinning/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "emitDecoratorMetadata": true, + "noEmit": true, + "jsx": "react-jsx", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/lit/column-pinning/vite.config.js b/examples/lit/column-pinning/vite.config.js new file mode 100644 index 0000000000..0e960eede3 --- /dev/null +++ b/examples/lit/column-pinning/vite.config.js @@ -0,0 +1,13 @@ +import { defineConfig } from 'vite' +import rollupReplace from '@rollup/plugin-replace' +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + ], +}) diff --git a/examples/lit/column-resizing-performant/index.html b/examples/lit/column-resizing-performant/index.html new file mode 100644 index 0000000000..120398a221 --- /dev/null +++ b/examples/lit/column-resizing-performant/index.html @@ -0,0 +1,13 @@ + + + + + + TanStack Lit Table - Column Resizing Performant + + +
+ + + + diff --git a/examples/lit/column-resizing-performant/package.json b/examples/lit/column-resizing-performant/package.json new file mode 100644 index 0000000000..491b9dda91 --- /dev/null +++ b/examples/lit/column-resizing-performant/package.json @@ -0,0 +1,21 @@ +{ + "name": "tanstack-lit-table-example-column-resizing-performant", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "lit": "^3.3.2" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/lit/column-resizing-performant/src/main.ts b/examples/lit/column-resizing-performant/src/main.ts new file mode 100644 index 0000000000..428924a2a6 --- /dev/null +++ b/examples/lit/column-resizing-performant/src/main.ts @@ -0,0 +1,481 @@ +import { customElement, state } from 'lit/decorators.js' +import { LitElement, html } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { styleMap } from 'lit/directives/style-map.js' +import { + FlexRender, + TableController, + columnResizingFeature, + columnSizingFeature, + tableFeatures, +} from '@tanstack/lit-table' +import { makeData } from './makeData' +import type { ColumnDef } from '@tanstack/lit-table' +import type { Person } from './makeData' + +const _features = tableFeatures({ + columnSizingFeature, + columnResizingFeature, +}) + +const columns: Array> = [ + { + header: 'Name', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'firstName', + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => info.getValue(), + header: () => html`Last Name`, + footer: (props) => props.column.id, + }, + ], + }, + { + header: 'Info', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'age', + header: () => 'Age', + footer: (props) => props.column.id, + }, + { + accessorKey: 'visits', + header: () => html`Visits`, + footer: (props) => props.column.id, + }, + { + accessorKey: 'status', + header: 'Status', + footer: (props) => props.column.id, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + footer: (props) => props.column.id, + }, + ], + }, +] + +@customElement('lit-table-example') +class LitTableExample extends LitElement { + @state() + private _data: Array = makeData(200) + + private tableController = new TableController(this) + + protected render() { + const table = this.tableController.table( + { + _features, + _rowModels: {}, + columns, + data: this._data, + defaultColumn: { minSize: 60, maxSize: 800 }, + columnResizeMode: 'onChange', + debugTable: true, + debugHeaders: true, + debugColumns: true, + }, + (state) => ({ + columnSizing: state.columnSizing, + columnResizing: state.columnResizing, + }), + ) + + // Compute CSS variables for column sizes + const columnSizeVars = (): Record => { + const headers = table.getFlatHeaders() + const colSizes: Record = {} + for (const header of headers) { + colSizes[`--header-${header.id}-size`] = `${header.getSize()}` + colSizes[`--col-${header.column.id}-size`] = + `${header.column.getSize()}` + } + return colSizes + } + + return html` +
+
+ + +
+ + This example has artificially slow cell renders to simulate complex + usage + +
+
+${JSON.stringify(table.state, null, 2)}
+
+ (${this._data.length.toLocaleString()} rows) +
+
+
+ ${repeat( + table.getHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` +
+ ${repeat( + headerGroup.headers, + (header) => header.id, + (header) => html` +
+ ${header.isPlaceholder + ? null + : FlexRender({ header })} +
+
+ `, + )} +
+ `, + )} +
+
+ ${repeat( + table.getRowModel().rows, + (row) => row.id, + (row) => html` +
+ ${repeat( + row.getAllCells(), + (cell) => cell.id, + (cell) => html` +
+ ${cell.renderValue()} +
+ `, + )} +
+ `, + )} +
+
+
+
+ + ` + } +} diff --git a/examples/lit/column-resizing-performant/src/makeData.ts b/examples/lit/column-resizing-performant/src/makeData.ts new file mode 100644 index 0000000000..d63c724b74 --- /dev/null +++ b/examples/lit/column-resizing-performant/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/lit/column-resizing-performant/tsconfig.json b/examples/lit/column-resizing-performant/tsconfig.json new file mode 100644 index 0000000000..02eb5e4c87 --- /dev/null +++ b/examples/lit/column-resizing-performant/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "emitDecoratorMetadata": true, + "noEmit": true, + "jsx": "react-jsx", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/lit/column-resizing-performant/vite.config.js b/examples/lit/column-resizing-performant/vite.config.js new file mode 100644 index 0000000000..0e960eede3 --- /dev/null +++ b/examples/lit/column-resizing-performant/vite.config.js @@ -0,0 +1,13 @@ +import { defineConfig } from 'vite' +import rollupReplace from '@rollup/plugin-replace' +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + ], +}) diff --git a/examples/lit/column-resizing/index.html b/examples/lit/column-resizing/index.html new file mode 100644 index 0000000000..553c5585ea --- /dev/null +++ b/examples/lit/column-resizing/index.html @@ -0,0 +1,13 @@ + + + + + + TanStack Lit Table - Column Resizing + + +
+ + + + diff --git a/examples/lit/column-resizing/package.json b/examples/lit/column-resizing/package.json new file mode 100644 index 0000000000..82940350bc --- /dev/null +++ b/examples/lit/column-resizing/package.json @@ -0,0 +1,21 @@ +{ + "name": "tanstack-lit-table-example-column-resizing", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "lit": "^3.3.2" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/lit/column-resizing/src/main.ts b/examples/lit/column-resizing/src/main.ts new file mode 100644 index 0000000000..8236c61756 --- /dev/null +++ b/examples/lit/column-resizing/src/main.ts @@ -0,0 +1,673 @@ +import { customElement, state } from 'lit/decorators.js' +import { LitElement, html } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { styleMap } from 'lit/directives/style-map.js' +import { + FlexRender, + TableController, + columnResizingFeature, + columnSizingFeature, + tableFeatures, +} from '@tanstack/lit-table' +import { makeData } from './makeData' +import type { + ColumnDef, + ColumnResizeDirection, + ColumnResizeMode, +} from '@tanstack/lit-table' +import type { Person } from './makeData' + +const _features = tableFeatures({ + columnResizingFeature, + columnSizingFeature, +}) + +const columns: Array> = [ + { + header: 'Name', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'firstName', + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => info.getValue(), + header: () => html`Last Name`, + footer: (props) => props.column.id, + }, + ], + }, + { + header: 'Info', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'age', + header: () => 'Age', + footer: (props) => props.column.id, + }, + { + header: 'More Info', + columns: [ + { + accessorKey: 'visits', + header: () => html`Visits`, + footer: (props) => props.column.id, + }, + { + accessorKey: 'status', + header: 'Status', + footer: (props) => props.column.id, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + footer: (props) => props.column.id, + }, + ], + }, + ], + }, +] + +@customElement('lit-table-example') +class LitTableExample extends LitElement { + private tableController = new TableController(this) + + @state() + private _data: Array = makeData(10) + + @state() + private columnResizeMode: ColumnResizeMode = 'onChange' + + @state() + private columnResizeDirection: ColumnResizeDirection = 'ltr' + + protected render() { + const table = this.tableController.table( + { + _features, + _rowModels: {}, + columns, + data: this._data, + columnResizeMode: this.columnResizeMode, + columnResizeDirection: this.columnResizeDirection, + debugTable: true, + debugHeaders: true, + debugColumns: true, + }, + (state) => ({ + columnSizing: state.columnSizing, + columnResizing: state.columnResizing, + }), + ) + + const resizerTransform = ( + header: ReturnType< + typeof table.getHeaderGroups + >[number]['headers'][number], + ) => { + if (this.columnResizeMode === 'onEnd' && header.column.getIsResizing()) { + const delta = table.store.state.columnResizing.deltaOffset ?? 0 + const dir = this.columnResizeDirection === 'rtl' ? -1 : 1 + return `translateX(${dir * delta}px)` + } + return '' + } + + return html` +
+
+ + +
+ + +
+
+
${''} +
+
+ + ${repeat( + table.getHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` + + ${repeat( + headerGroup.headers, + (header) => header.id, + (header) => html` + + `, + )} + + `, + )} + + + ${repeat( + table.getRowModel().rows, + (row) => row.id, + (row) => html` + + ${repeat( + row.getAllCells(), + (cell) => cell.id, + (cell) => html` + + `, + )} + + `, + )} + +
+ ${header.isPlaceholder + ? null + : FlexRender({ header })} +
+
+ ${FlexRender({ cell })} +
+
+
+
${'
(relative)'}
+
+
+
+ ${repeat( + table.getHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` +
+ ${repeat( + headerGroup.headers, + (header) => header.id, + (header) => html` +
+ ${header.isPlaceholder + ? null + : FlexRender({ header })} +
+
+ `, + )} +
+ `, + )} +
+
+ ${repeat( + table.getRowModel().rows, + (row) => row.id, + (row) => html` +
+ ${repeat( + row.getAllCells(), + (cell) => cell.id, + (cell) => html` +
+ ${FlexRender({ cell })} +
+ `, + )} +
+ `, + )} +
+
+
+
+
${'
(absolute positioning)'}
+
+
+
+ ${repeat( + table.getHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` +
+ ${repeat( + headerGroup.headers, + (header) => header.id, + (header) => html` +
+ ${header.isPlaceholder + ? null + : FlexRender({ header })} +
+
+ `, + )} +
+ `, + )} +
+
+ ${repeat( + table.getRowModel().rows, + (row) => row.id, + (row) => html` +
+ ${repeat( + row.getAllCells(), + (cell) => cell.id, + (cell) => html` +
+ ${FlexRender({ cell })} +
+ `, + )} +
+ `, + )} +
+
+
+
+
+
${JSON.stringify(table.state, null, 2)}
+
+ + ` + } +} diff --git a/examples/lit/column-resizing/src/makeData.ts b/examples/lit/column-resizing/src/makeData.ts new file mode 100644 index 0000000000..fd239ee360 --- /dev/null +++ b/examples/lit/column-resizing/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + status: string + progress: number + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/lit/column-resizing/tsconfig.json b/examples/lit/column-resizing/tsconfig.json new file mode 100644 index 0000000000..02eb5e4c87 --- /dev/null +++ b/examples/lit/column-resizing/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "emitDecoratorMetadata": true, + "noEmit": true, + "jsx": "react-jsx", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/lit/column-resizing/vite.config.js b/examples/lit/column-resizing/vite.config.js new file mode 100644 index 0000000000..0e960eede3 --- /dev/null +++ b/examples/lit/column-resizing/vite.config.js @@ -0,0 +1,13 @@ +import { defineConfig } from 'vite' +import rollupReplace from '@rollup/plugin-replace' +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + ], +}) diff --git a/examples/lit/column-sizing/index.html b/examples/lit/column-sizing/index.html index 9807874fae..15b0437b92 100644 --- a/examples/lit/column-sizing/index.html +++ b/examples/lit/column-sizing/index.html @@ -4,7 +4,6 @@ Vite App -
diff --git a/examples/lit/column-sizing/package.json b/examples/lit/column-sizing/package.json index 6603b18bb3..a1867dc59a 100644 --- a/examples/lit/column-sizing/package.json +++ b/examples/lit/column-sizing/package.json @@ -1,21 +1,21 @@ { "name": "tanstack-lit-table-example-column-sizing", - "version": "0.0.0", "private": true, "scripts": { "dev": "vite", "build": "vite build", "serve": "vite preview", - "start": "vite" + "start": "vite", + "lint": "eslint ./src" }, "dependencies": { - "@faker-js/faker": "^8.4.1", - "@tanstack/lit-table": "^8.20.5", - "lit": "^3.1.4" + "@faker-js/faker": "^10.4.0", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "lit": "^3.3.2" }, "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/lit/column-sizing/src/main.ts b/examples/lit/column-sizing/src/main.ts index 937a39ff6b..c11d130bb2 100644 --- a/examples/lit/column-sizing/src/main.ts +++ b/examples/lit/column-sizing/src/main.ts @@ -1,25 +1,30 @@ -import { customElement } from 'lit/decorators.js' -import { html, LitElement } from 'lit' +import { customElement, state } from 'lit/decorators.js' +import { LitElement, html } from 'lit' import { repeat } from 'lit/directives/repeat.js' -import { state } from 'lit/decorators/state.js' import { ColumnDef, - flexRender, - getCoreRowModel, + FlexRender, TableController, + columnResizingFeature, + columnSizingFeature, + tableFeatures, } from '@tanstack/lit-table' +import { Person, makeData } from './makeData' -import { makeData, Person } from './makeData' +const _features = tableFeatures({ + columnSizingFeature, + columnResizingFeature, +}) -const columns: ColumnDef[] = [ +const columns: Array> = [ { accessorKey: 'firstName', - cell: info => info.getValue(), + cell: (info) => info.getValue(), }, { - accessorFn: row => row.lastName, + accessorFn: (row) => row.lastName, id: 'lastName', - cell: info => info.getValue(), + cell: (info) => info.getValue(), header: () => html`Last Name`, }, { @@ -41,7 +46,6 @@ const columns: ColumnDef[] = [ { accessorKey: 'rank', header: 'Rank', - invertSorting: true, //invert the sorting order (golf score-like where smaller is better) }, { accessorKey: 'createdAt', @@ -49,43 +53,63 @@ const columns: ColumnDef[] = [ }, ] -const data: Person[] = makeData(1000) - @customElement('lit-table-example') class LitTableExample extends LitElement { @state() - private tableController = new TableController(this) + private _data: Array = makeData(1_000) + + private tableController = new TableController(this) protected render() { - const table = this.tableController.table({ - data, - columns, - columnResizeMode: 'onChange', - columnResizeDirection: 'ltr', - getCoreRowModel: getCoreRowModel(), - debugTable: true, - debugHeaders: true, - debugColumns: true, - }) + const table = this.tableController.table( + { + _features, + _rowModels: {}, + data: this._data, + columns, + columnResizeMode: 'onChange', + columnResizeDirection: 'ltr', + debugTable: true, + debugHeaders: true, + debugColumns: true, + }, + (state) => ({ + columnSizing: state.columnSizing, + columnResizing: state.columnResizing, + }), + ) return html` +
+ + +
${repeat( table.getHeaderGroups(), - headerGroup => headerGroup.id, - headerGroup => html` + (headerGroup) => headerGroup.id, + (headerGroup) => html` ${headerGroup.headers.map( - header => html` + (header) => html` - ` + `, )} - ` + `, )} @@ -109,35 +133,17 @@ class LitTableExample extends LitElement { .getRowModel() .rows.slice(0, 10) .map( - row => html` + (row) => html` ${row - .getVisibleCells() - .map( - cell => html` - - ` - )} + .getAllCells() + .map((cell) => html` `)} - ` + `, )}
- ${flexRender( - header.column.columnDef.header, - header.getContext() - )} + ${FlexRender({ header })} ${header.isPlaceholder ? null : html`
`}
- ${flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - ${FlexRender({ cell })}
-
-${JSON.stringify(
-          {
-            columnSizing: table.getState().columnSizing,
-            columnSizingInfo: table.getState().columnSizingInfo,
-          },
-          null,
-          2
-        )}
+
${JSON.stringify(table.state, null, 2)}
` } diff --git a/examples/lit/column-sizing/src/makeData.ts b/examples/lit/column-sizing/src/makeData.ts index d6c0639b22..fc070cd5d2 100644 --- a/examples/lit/column-sizing/src/makeData.ts +++ b/examples/lit/column-sizing/src/makeData.ts @@ -9,11 +9,11 @@ export type Person = { status: 'relationship' | 'complicated' | 'single' rank: number createdAt: Date - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -32,14 +32,14 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], rank: faker.number.int(100), } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] return range(len).map((_d): Person => { return { ...newPerson(), diff --git a/examples/lit/column-sizing/tsconfig.json b/examples/lit/column-sizing/tsconfig.json index 56517d3a72..25627d684c 100644 --- a/examples/lit/column-sizing/tsconfig.json +++ b/examples/lit/column-sizing/tsconfig.json @@ -4,8 +4,6 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, @@ -14,12 +12,10 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "useDefineForClassFields": false, - - /* Linting */ "strict": true, "noUnusedLocals": false, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["src"] + "include": ["src", "vite.config.js", "vite.config.ts"] } diff --git a/examples/lit/column-visibility/index.html b/examples/lit/column-visibility/index.html new file mode 100644 index 0000000000..4efe8abcf9 --- /dev/null +++ b/examples/lit/column-visibility/index.html @@ -0,0 +1,13 @@ + + + + + + TanStack Lit Table - Column Visibility + + +
+ + + + diff --git a/examples/lit/column-visibility/package.json b/examples/lit/column-visibility/package.json new file mode 100644 index 0000000000..3e82659147 --- /dev/null +++ b/examples/lit/column-visibility/package.json @@ -0,0 +1,21 @@ +{ + "name": "tanstack-lit-table-example-column-visibility", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "lit": "^3.3.2" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/lit/column-visibility/src/main.ts b/examples/lit/column-visibility/src/main.ts new file mode 100644 index 0000000000..1980b3fba3 --- /dev/null +++ b/examples/lit/column-visibility/src/main.ts @@ -0,0 +1,426 @@ +import { customElement, state } from 'lit/decorators.js' +import { LitElement, html } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { + FlexRender, + TableController, + columnVisibilityFeature, + tableFeatures, +} from '@tanstack/lit-table' +import { makeData } from './makeData' +import type { ColumnDef } from '@tanstack/lit-table' +import type { Person } from './makeData' + +const _features = tableFeatures({ + columnVisibilityFeature, +}) + +const columns: Array> = [ + { + header: 'Name', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'firstName', + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => info.getValue(), + header: () => html`Last Name`, + footer: (props) => props.column.id, + }, + ], + }, + { + header: 'Info', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'age', + header: () => 'Age', + footer: (props) => props.column.id, + }, + { + header: 'More Info', + columns: [ + { + accessorKey: 'visits', + header: () => html`Visits`, + footer: (props) => props.column.id, + }, + { + accessorKey: 'status', + header: 'Status', + footer: (props) => props.column.id, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + footer: (props) => props.column.id, + }, + ], + }, + ], + }, +] + +@customElement('lit-table-example') +class LitTableExample extends LitElement { + @state() + private _data: Array = makeData(20) + + private tableController = new TableController(this) + + protected render() { + const table = this.tableController.table( + { + _features, + _rowModels: {}, + columns, + data: this._data, + debugTable: true, + }, + (state) => ({ columnVisibility: state.columnVisibility }), + ) + + return html` +
+
+ + +
+
+
+
+ +
+ ${table.getAllLeafColumns().map( + (column) => html` +
+ +
+ `, + )} +
+
+ + + ${repeat( + table.getHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` + + ${headerGroup.headers.map( + (header) => html` + + `, + )} + + `, + )} + + + ${table.getRowModel().rows.map( + (row) => html` + + ${row + .getVisibleCells() + .map((cell) => html` `)} + + `, + )} + + + ${repeat( + table.getFooterGroups(), + (footerGroup) => footerGroup.id, + (footerGroup) => html` + + ${footerGroup.headers.map( + (header) => html` + + `, + )} + + `, + )} + +
+ ${header.isPlaceholder ? null : FlexRender({ header })} +
${FlexRender({ cell })}
+ ${header.isPlaceholder + ? null + : FlexRender({ footer: header })} +
+
+
${JSON.stringify(table.state, null, 2)}
+
+ + ` + } +} diff --git a/examples/lit/column-visibility/src/makeData.ts b/examples/lit/column-visibility/src/makeData.ts new file mode 100644 index 0000000000..d274a17f45 --- /dev/null +++ b/examples/lit/column-visibility/src/makeData.ts @@ -0,0 +1,47 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + return makeDataLevel() +} diff --git a/examples/lit/column-visibility/tsconfig.json b/examples/lit/column-visibility/tsconfig.json new file mode 100644 index 0000000000..02eb5e4c87 --- /dev/null +++ b/examples/lit/column-visibility/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "emitDecoratorMetadata": true, + "noEmit": true, + "jsx": "react-jsx", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/lit/column-visibility/vite.config.js b/examples/lit/column-visibility/vite.config.js new file mode 100644 index 0000000000..75dafbd933 --- /dev/null +++ b/examples/lit/column-visibility/vite.config.js @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite' +import rollupReplace from '@rollup/plugin-replace' + +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + ], +}) diff --git a/examples/lit/composable-tables/index.html b/examples/lit/composable-tables/index.html new file mode 100644 index 0000000000..2acade2962 --- /dev/null +++ b/examples/lit/composable-tables/index.html @@ -0,0 +1,13 @@ + + + + + + TanStack Lit Table - Composable Tables + + + + + + + diff --git a/examples/lit/composable-tables/package.json b/examples/lit/composable-tables/package.json new file mode 100644 index 0000000000..e526ac7893 --- /dev/null +++ b/examples/lit/composable-tables/package.json @@ -0,0 +1,22 @@ +{ + "name": "tanstack-lit-table-example-composable-tables", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@lit/context": "^1.1.6", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "lit": "^3.3.2" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/lit/composable-tables/src/components/cell-components.ts b/examples/lit/composable-tables/src/components/cell-components.ts new file mode 100644 index 0000000000..414bc5e7b1 --- /dev/null +++ b/examples/lit/composable-tables/src/components/cell-components.ts @@ -0,0 +1,64 @@ +import { html } from 'lit' +import type { Cell } from '@tanstack/lit-table' + +// Cell components are plain functions that receive the cell instance as their +// first argument (bound automatically by AppCell in createTableHook). +// In column definitions, call them as: cell: ({ cell }) => cell.TextCell() + +export function TextCell(cell: Cell) { + return html`${String(cell.getValue() ?? '')}` +} + +export function NumberCell(cell: Cell) { + return html`${Number(cell.getValue() ?? 0).toLocaleString()}` +} + +export function StatusCell(cell: Cell) { + const status = String(cell.getValue() ?? '') + const statusClass = + status === 'single' + ? 'single' + : status === 'complicated' + ? 'complicated' + : 'relationship' + return html`${status}` +} + +export function ProgressCell(cell: Cell) { + const value = Number(cell.getValue() ?? 0) + return html` +
+
+
+ ` +} + +export function RowActionsCell(cell: Cell) { + const row = cell.row.original as { + firstName?: string + name?: string + } + const name = row.firstName ?? row.name ?? 'row' + return html` +
+ + + +
+ ` +} + +export function PriceCell(cell: Cell) { + const value = Number(cell.getValue() ?? 0) + return html`$${value.toLocaleString(undefined, { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })}` +} + +export function CategoryCell(cell: Cell) { + const category = String(cell.getValue() ?? '') + return html`${category}` +} diff --git a/examples/lit/composable-tables/src/components/header-components.ts b/examples/lit/composable-tables/src/components/header-components.ts new file mode 100644 index 0000000000..f68508b614 --- /dev/null +++ b/examples/lit/composable-tables/src/components/header-components.ts @@ -0,0 +1,44 @@ +import { html, nothing } from 'lit' +import type { Header } from '@tanstack/lit-table' + +// Header components are plain functions that receive the header instance as +// their first argument (bound automatically by AppHeader/AppFooter in createTableHook). +// In column definitions, call them as: header.SortIndicator() + +export function SortIndicator(header: Header) { + const sorted = header.column.getIsSorted() + if (!sorted) return nothing + return html`${sorted === 'asc' ? '🔼' : '🔽'}` +} + +export function ColumnFilter(header: Header) { + if (!header.column.getCanFilter()) return nothing + const value = (header.column.getFilterValue() ?? '') as string + return html` +
e.stopPropagation()}> + + header.column.setFilterValue((e.target as HTMLInputElement).value)} + placeholder="Filter ${header.column.id}..." + /> +
+ ` +} + +export function FooterColumnId(header: Header) { + return html`${header.column.id}` +} + +export function FooterSum(header: Header) { + const table = header.getContext().table + const rows = table.getFilteredRowModel().rows + const sum = rows.reduce((acc, row) => { + const value = row.getValue(header.column.id) + return acc + (typeof value === 'number' ? value : 0) + }, 0) + return html`${sum > 0 ? sum.toLocaleString() : '—'}` +} diff --git a/examples/lit/composable-tables/src/components/products-table.ts b/examples/lit/composable-tables/src/components/products-table.ts new file mode 100644 index 0000000000..96d75df3c4 --- /dev/null +++ b/examples/lit/composable-tables/src/components/products-table.ts @@ -0,0 +1,196 @@ +import { LitElement, html, nothing } from 'lit' +import { customElement, state } from 'lit/decorators.js' +import { createAppColumnHelper, useAppTable } from '../hooks/table' +import { makeProductData } from '../makeData' +import type { Product } from '../makeData' +// Import table-level custom elements so they are registered +import './table-components' + +// Create the column helper pre-bound with features — only need the data type! +const productColumnHelper = createAppColumnHelper() + +// Define columns — different structure than Users table, same reusable components +const columns = productColumnHelper.columns([ + productColumnHelper.accessor('name', { + header: 'Product Name', + footer: (props) => props.column.id, + cell: ({ cell }) => cell.TextCell(), + }), + productColumnHelper.accessor('category', { + header: 'Category', + footer: (props) => props.column.id, + cell: ({ cell }) => cell.CategoryCell(), + }), + productColumnHelper.accessor('price', { + header: 'Price', + footer: (props) => props.column.id, + cell: ({ cell }) => cell.PriceCell(), + }), + productColumnHelper.accessor('stock', { + header: 'In Stock', + footer: (props) => props.column.id, + cell: ({ cell }) => cell.NumberCell(), + }), + productColumnHelper.accessor('rating', { + header: 'Rating', + footer: (props) => props.column.id, + cell: ({ cell }) => cell.ProgressCell(), + }), +]) + +@customElement('products-table') +export class ProductsTable extends LitElement { + @state() + private data = makeProductData(500) + + // Create the table using the same useAppTable hook + // NOTE: Capture `this` as `host` to avoid infinite recursion in the getter + private appTable = (() => { + const host = this + return useAppTable( + this, + { + debugTable: true, + columns, + get data() { + return host.data + }, + }, + (state) => ({ + pagination: state.pagination, + sorting: state.sorting, + columnFilters: state.columnFilters, + }), + ) + })() + + private refreshData = () => { + this.data = makeProductData(500) + } + + createRenderRoot() { + return this + } + + protected render() { + const table = this.appTable.table() + const { sorting, columnFilters } = table.state + + return html` +
+ + { + this.data = makeProductData(200_000) + }} + > + + + + + ${table.getHeaderGroups().map( + (headerGroup) => html` + + ${headerGroup.headers.map((h) => + table.AppHeader( + h, + (header) => html` + + `, + ), + )} + + `, + )} + + + ${table.getRowModel().rows.map( + (row) => html` + + ${row + .getAllCells() + .map((c) => + table.AppCell( + c, + (cell) => html` `, + ), + )} + + `, + )} + + + ${table.getFooterGroups().map( + (footerGroup) => html` + + ${footerGroup.headers.map((f) => + table.AppFooter(f, (footer) => { + const columnId = footer.column.id + const hasFilter = columnFilters.some( + (cf) => cf.id === columnId, + ) + return html` + + ` + }), + )} + + `, + )} + +
+ ${header.isPlaceholder + ? nothing + : html` + ${header.FlexRender()} ${header.SortIndicator()} + ${header.ColumnFilter()} + ${sorting.length > 1 && + sorting.findIndex( + (s) => s.id === header.column.id, + ) > -1 + ? html`${sorting.findIndex( + (s) => s.id === header.column.id, + ) + 1}` + : nothing} + `} +
${cell.FlexRender()}
+ ${footer.isPlaceholder + ? nothing + : columnId === 'price' || + columnId === 'stock' || + columnId === 'rating' + ? html` + ${footer.FooterSum()} + ${hasFilter + ? html` + (filtered)` + : nothing} + ` + : html` + ${footer.FooterColumnId()} + ${hasFilter + ? html` + ✓` + : nothing} + `} +
+ + + + +
+ ` + } +} diff --git a/examples/lit/composable-tables/src/components/table-components.ts b/examples/lit/composable-tables/src/components/table-components.ts new file mode 100644 index 0000000000..bea12f1cc0 --- /dev/null +++ b/examples/lit/composable-tables/src/components/table-components.ts @@ -0,0 +1,147 @@ +import { LitElement, html, nothing } from 'lit' +import { customElement, property } from 'lit/decorators.js' +import { useTableContext } from '../hooks/table' + +// Table-level components are LitElement custom elements that use +// useTableContext(this) to access the table instance from the nearest +// ancestor that called useAppTable. They render in light DOM so that +// global CSS applies and @lit/context events bubble correctly. + +@customElement('pagination-controls') +export class PaginationControls extends LitElement { + private _table = useTableContext(this) + + createRenderRoot() { + return this + } + + protected render() { + const table = this._table.value + if (!table) return nothing + + return html` + + ` + } +} + +@customElement('row-count') +export class RowCount extends LitElement { + private _table = useTableContext(this) + + createRenderRoot() { + return this + } + + protected render() { + const table = this._table.value + if (!table) return nothing + + return html` +
+ Showing ${table.getRowModel().rows.length.toLocaleString()} of + ${table.getRowCount().toLocaleString()} rows +
+ ` + } +} + +@customElement('table-toolbar') +export class TableToolbar extends LitElement { + private _table = useTableContext(this) + + @property() + declare title: string + + @property({ attribute: false }) + declare onRefresh: (() => void) | undefined + + @property({ attribute: false }) + declare onStressTest: (() => void) | undefined + + createRenderRoot() { + return this + } + + protected render() { + const table = this._table.value + if (!table) return nothing + + return html` +
+

${this.title}

+
+ ${this.onRefresh + ? html`` + : nothing} + ${this.onStressTest + ? html`` + : nothing} + + +
+
+ ` + } +} diff --git a/examples/lit/composable-tables/src/components/users-table.ts b/examples/lit/composable-tables/src/components/users-table.ts new file mode 100644 index 0000000000..a13e7fe54c --- /dev/null +++ b/examples/lit/composable-tables/src/components/users-table.ts @@ -0,0 +1,212 @@ +import { LitElement, html, nothing } from 'lit' +import { customElement, state } from 'lit/decorators.js' +import { createAppColumnHelper, useAppTable } from '../hooks/table' +import { makeData } from '../makeData' +import type { Person } from '../makeData' +// Import table-level custom elements so they are registered +import './table-components' + +// Create the column helper pre-bound with features — only need the data type! +const personColumnHelper = createAppColumnHelper() + +// Define columns using the column helper. +// NOTE: Use createAppColumnHelper (not createColumnHelper) to get pre-bound +// component types on cell/header objects (e.g., cell.TextCell, header.SortIndicator). +const columns = personColumnHelper.columns([ + personColumnHelper.accessor('firstName', { + header: 'First Name', + footer: (props) => props.column.id, + cell: ({ cell }) => cell.TextCell(), + }), + personColumnHelper.accessor('lastName', { + header: 'Last Name', + footer: (props) => props.column.id, + cell: ({ cell }) => cell.TextCell(), + }), + personColumnHelper.accessor('age', { + header: 'Age', + footer: (props) => props.column.id, + cell: ({ cell }) => cell.NumberCell(), + }), + personColumnHelper.accessor('visits', { + header: 'Visits', + footer: (props) => props.column.id, + cell: ({ cell }) => cell.NumberCell(), + }), + personColumnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + cell: ({ cell }) => cell.StatusCell(), + }), + personColumnHelper.accessor('progress', { + header: 'Progress', + footer: (props) => props.column.id, + cell: ({ cell }) => cell.ProgressCell(), + }), + personColumnHelper.display({ + id: 'actions', + header: 'Actions', + cell: ({ cell }) => cell.RowActionsCell(), + }), +]) + +@customElement('users-table') +export class UsersTable extends LitElement { + @state() + private data = makeData(1000) + + // Create the table — _features and _rowModels are already configured! + // NOTE: We capture `this` (the LitElement) as `host` because inside the + // object literal's getter, `this` would refer to the options object itself + // (causing infinite recursion if we wrote `get data() { return this.data }`). + private appTable = (() => { + const host = this + return useAppTable( + this, + { + columns, + get data() { + return host.data + }, + debugTable: true, + }, + (state) => ({ + pagination: state.pagination, + sorting: state.sorting, + columnFilters: state.columnFilters, + }), + ) + })() + + private refreshData = () => { + this.data = makeData(1000) + } + + createRenderRoot() { + return this + } + + protected render() { + const table = this.appTable.table() + const { sorting, columnFilters } = table.state + + return html` +
+ + { + this.data = makeData(200_000) + }} + > + + + + + ${table.getHeaderGroups().map( + (headerGroup) => html` + + ${headerGroup.headers.map((h) => + table.AppHeader( + h, + (header) => html` + + `, + ), + )} + + `, + )} + + + ${table.getRowModel().rows.map( + (row) => html` + + ${row + .getAllCells() + .map((c) => + table.AppCell( + c, + (cell) => html` `, + ), + )} + + `, + )} + + + ${table.getFooterGroups().map( + (footerGroup) => html` + + ${footerGroup.headers.map((f) => + table.AppFooter(f, (footer) => { + const columnId = footer.column.id + const hasFilter = columnFilters.some( + (cf) => cf.id === columnId, + ) + return html` + + ` + }), + )} + + `, + )} + +
+ ${header.isPlaceholder + ? nothing + : html` + ${header.FlexRender()} ${header.SortIndicator()} + ${header.ColumnFilter()} + ${sorting.length > 1 && + sorting.findIndex( + (s) => s.id === header.column.id, + ) > -1 + ? html`${sorting.findIndex( + (s) => s.id === header.column.id, + ) + 1}` + : nothing} + `} +
${cell.FlexRender()}
+ ${footer.isPlaceholder + ? nothing + : columnId === 'age' || + columnId === 'visits' || + columnId === 'progress' + ? html` + ${footer.FooterSum()} + ${hasFilter + ? html` + (filtered)` + : nothing} + ` + : columnId === 'actions' + ? nothing + : html` + ${footer.FooterColumnId()} + ${hasFilter + ? html` + ✓` + : nothing} + `} +
+ + + + +
+ ` + } +} diff --git a/examples/lit/composable-tables/src/hooks/table.ts b/examples/lit/composable-tables/src/hooks/table.ts new file mode 100644 index 0000000000..a74ff16b8d --- /dev/null +++ b/examples/lit/composable-tables/src/hooks/table.ts @@ -0,0 +1,61 @@ +import { + columnFilteringFeature, + createFilteredRowModel, + createPaginatedRowModel, + createSortedRowModel, + createTableHook, + filterFns, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/lit-table' +import { + CategoryCell, + NumberCell, + PriceCell, + ProgressCell, + RowActionsCell, + StatusCell, + TextCell, +} from '../components/cell-components' +import { + ColumnFilter, + FooterColumnId, + FooterSum, + SortIndicator, +} from '../components/header-components' + +// Note: Table-level components (PaginationControls, RowCount, TableToolbar) +// are LitElement custom elements that use useTableContext(this) directly, +// so they don't need to be registered here as tableComponents. + +export const { createAppColumnHelper, useAppTable, useTableContext } = + createTableHook({ + _features: tableFeatures({ + columnFilteringFeature, + rowPaginationFeature, + rowSortingFeature, + }), + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + }, + getRowId: (row) => row.id, + cellComponents: { + TextCell, + NumberCell, + StatusCell, + ProgressCell, + RowActionsCell, + PriceCell, + CategoryCell, + }, + headerComponents: { + SortIndicator, + ColumnFilter, + FooterColumnId, + FooterSum, + }, + }) diff --git a/examples/lit/composable-tables/src/index.css b/examples/lit/composable-tables/src/index.css new file mode 100644 index 0000000000..4d8fef0142 --- /dev/null +++ b/examples/lit/composable-tables/src/index.css @@ -0,0 +1,605 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + width: 100%; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th, +td { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 8px 12px; + text-align: left; +} + +th { + background-color: #f5f5f5; + font-weight: 600; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +.table-container { + padding: 16px; + max-width: 1200px; + margin: 0 auto; + border-collapse: collapse; + border-spacing: 0; +} + +.pagination { + display: flex; + align-items: center; + gap: 8px; + margin-top: 16px; + flex-wrap: wrap; +} + +.pagination button { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px 8px; + cursor: pointer; + background: white; +} + +.pagination button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.pagination input { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px; + width: 64px; +} + +.pagination select { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px; +} + +.sort-indicator { + margin-left: 4px; +} + +.column-filter { + margin-top: 4px; +} + +.column-filter input { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px; + width: 100%; + font-size: 12px; +} + +.sortable-header { + cursor: pointer; + user-select: none; +} + +.sortable-header:hover { + background-color: #e8e8e8; +} + +.row-actions { + display: flex; + gap: 4px; +} + +.row-actions button { + border: 1px solid #ccc; + border-radius: 4px; + padding: 2px 8px; + cursor: pointer; + background: white; + font-size: 12px; +} + +.row-actions button:hover { + background-color: #f0f0f0; +} + +.status-badge { + display: inline-block; + padding: 2px 8px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; +} + +.status-badge.relationship { + background-color: #d4edda; + color: #155724; +} + +.status-badge.complicated { + background-color: #fff3cd; + color: #856404; +} + +.status-badge.single { + background-color: #cce5ff; + color: #004085; +} + +.progress-bar { + width: 100%; + height: 8px; + background-color: #e9ecef; + border-radius: 4px; + overflow: hidden; +} + +.progress-bar-fill { + height: 100%; + background-color: #007bff; + transition: width 0.2s; +} + +.table-toolbar { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + flex-wrap: wrap; + gap: 8px; + border-collapse: collapse; + border-spacing: 0; +} + +.table-toolbar h2 { + margin: 0; + border-collapse: collapse; + border-spacing: 0; +} + +.table-toolbar-actions { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.table-toolbar button { + border: 1px solid #ccc; + border-radius: 4px; + padding: 8px 16px; + cursor: pointer; + background: white; + border-collapse: collapse; + border-spacing: 0; +} + +.table-toolbar button:hover { + background-color: #f0f0f0; + border-collapse: collapse; + border-spacing: 0; +} + +.row-count { + color: #666; + font-size: 14px; + margin-top: 8px; +} + +.app { + padding: 16px; +} + +.app h1 { + text-align: center; + margin-bottom: 8px; +} + +.description { + text-align: center; + color: #666; + margin-bottom: 32px; +} + +.description code { + background-color: #f5f5f5; + padding: 2px 6px; + border-radius: 4px; + font-size: 13px; +} + +.table-divider { + height: 48px; + border-bottom: 1px solid #e0e0e0; + margin: 32px auto; + max-width: 1200px; + border-collapse: collapse; + border-spacing: 0; +} + +.category-badge { + display: inline-block; + padding: 2px 8px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; + text-transform: capitalize; +} + +.category-badge.electronics { + background-color: #e3f2fd; + color: #1565c0; +} + +.category-badge.clothing { + background-color: #fce4ec; + color: #c2185b; +} + +.category-badge.food { + background-color: #e8f5e9; + color: #2e7d32; +} + +.category-badge.books { + background-color: #fff8e1; + color: #f57c00; +} + +.price { + font-weight: 600; + color: #2e7d32; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/lit/composable-tables/src/main.ts b/examples/lit/composable-tables/src/main.ts new file mode 100644 index 0000000000..c5f9f66ff2 --- /dev/null +++ b/examples/lit/composable-tables/src/main.ts @@ -0,0 +1,32 @@ +import { LitElement, html } from 'lit' +import { customElement } from 'lit/decorators.js' +import './components/users-table' +import './components/products-table' + +@customElement('app-root') +class AppRoot extends LitElement { + createRenderRoot() { + return this + } + + protected render() { + return html` +
+

Composable Tables Example

+

+ Both tables below use the same useAppTable hook and + shareable components, but with different data types and column + configurations. +

+ + + + +
+ + + +
+ ` + } +} diff --git a/examples/lit/composable-tables/src/makeData.ts b/examples/lit/composable-tables/src/makeData.ts new file mode 100644 index 0000000000..17dec1c6de --- /dev/null +++ b/examples/lit/composable-tables/src/makeData.ts @@ -0,0 +1,77 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +export type Product = { + id: string + name: string + category: 'electronics' | 'clothing' | 'food' | 'books' + price: number + stock: number + rating: number +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +const newProduct = (): Product => { + return { + id: faker.string.uuid(), + name: faker.commerce.productName(), + category: faker.helpers.shuffle([ + 'electronics', + 'clothing', + 'food', + 'books', + ])[0], + price: parseFloat(faker.commerce.price({ min: 5, max: 500 })), + stock: faker.number.int({ min: 0, max: 200 }), + rating: faker.number.int({ min: 0, max: 100 }), + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} + +export function makeProductData(count: number): Array { + return range(count).map(() => newProduct()) +} diff --git a/examples/lit/composable-tables/tsconfig.json b/examples/lit/composable-tables/tsconfig.json new file mode 100644 index 0000000000..02eb5e4c87 --- /dev/null +++ b/examples/lit/composable-tables/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "emitDecoratorMetadata": true, + "noEmit": true, + "jsx": "react-jsx", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/lit/composable-tables/vite.config.js b/examples/lit/composable-tables/vite.config.js new file mode 100644 index 0000000000..fa3b238ac6 --- /dev/null +++ b/examples/lit/composable-tables/vite.config.js @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + ], +}) diff --git a/examples/lit/expanding/index.html b/examples/lit/expanding/index.html new file mode 100644 index 0000000000..3691ffa308 --- /dev/null +++ b/examples/lit/expanding/index.html @@ -0,0 +1,13 @@ + + + + + + TanStack Lit Table - Expanding + + +
+ + + + diff --git a/examples/lit/expanding/package.json b/examples/lit/expanding/package.json new file mode 100644 index 0000000000..7a5c1c9934 --- /dev/null +++ b/examples/lit/expanding/package.json @@ -0,0 +1,21 @@ +{ + "name": "tanstack-lit-table-example-expanding", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "lit": "^3.3.2" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/lit/expanding/src/main.ts b/examples/lit/expanding/src/main.ts new file mode 100644 index 0000000000..8b6064e487 --- /dev/null +++ b/examples/lit/expanding/src/main.ts @@ -0,0 +1,576 @@ +import { customElement, state } from 'lit/decorators.js' +import { LitElement, html } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { + FlexRender, + TableController, + columnFilteringFeature, + createExpandedRowModel, + createFilteredRowModel, + createPaginatedRowModel, + createSortedRowModel, + filterFns, + rowExpandingFeature, + rowPaginationFeature, + rowSelectionFeature, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/lit-table' +import { makeData } from './makeData' +import type { Column, ColumnDef, Table } from '@tanstack/lit-table' +import type { Person } from './makeData' + +const _features = tableFeatures({ + columnFilteringFeature, + rowExpandingFeature, + rowPaginationFeature, + rowSortingFeature, + rowSelectionFeature, +}) + +function indeterminateCheckbox(options: { + checked: boolean + indeterminate: boolean + onChange: (e: Event) => void +}) { + return html` + + ` +} + +function renderFilter( + column: Column, + table: Table, +) { + const firstValue = table + .getPreFilteredRowModel() + .flatRows[0]?.getValue(column.id) + + const columnFilterValue = column.getFilterValue() + + if (typeof firstValue === 'number') { + return html` +
+ + +
+ ` + } + + return html` + + ` +} + +const columns: Array> = [ + { + accessorKey: 'firstName', + header: ({ table }) => html` + ${indeterminateCheckbox({ + checked: table.getIsAllRowsSelected(), + indeterminate: table.getIsSomeRowsSelected(), + onChange: table.getToggleAllRowsSelectedHandler(), + })} + + First Name + `, + cell: ({ row, getValue }) => html` +
+ ${indeterminateCheckbox({ + checked: row.getIsSelected(), + indeterminate: row.getIsSomeSelected(), + onChange: row.getToggleSelectedHandler(), + })} + ${row.getCanExpand() + ? html`` + : '🔵'} + ${getValue()} +
+ `, + footer: (props) => props.column.id, + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => info.getValue(), + header: () => html`Last Name`, + footer: (props) => props.column.id, + }, + { + accessorKey: 'age', + header: () => 'Age', + footer: (props) => props.column.id, + }, + { + accessorKey: 'visits', + header: () => html`Visits`, + footer: (props) => props.column.id, + }, + { + accessorKey: 'status', + header: 'Status', + footer: (props) => props.column.id, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + footer: (props) => props.column.id, + }, +] + +@customElement('lit-table-example') +class LitTableExample extends LitElement { + @state() + private _data: Array = makeData(100, 5, 3) + + private tableController = new TableController(this) + + protected render() { + const table = this.tableController.table( + { + _features, + _rowModels: { + expandedRowModel: createExpandedRowModel(), + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, + columns, + data: this._data, + getSubRows: (row) => row.subRows, + debugTable: true, + }, + (state) => ({ + expanded: state.expanded, + rowSelection: state.rowSelection, + columnFilters: state.columnFilters, + pagination: state.pagination, + sorting: state.sorting, + }), + ) + + return html` +
+
+ + +
+
+ + + ${repeat( + table.getHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` + + ${headerGroup.headers.map( + (header) => html` + + `, + )} + + `, + )} + + + ${table.getRowModel().rows.map( + (row) => html` + + ${row + .getAllCells() + .map((cell) => html` `)} + + `, + )} + +
+ ${header.isPlaceholder + ? null + : html` +
+ ${FlexRender({ header })} + ${header.column.getCanFilter() + ? html`
+ ${renderFilter(header.column, table)} +
` + : null} +
+ `} +
${FlexRender({ cell })}
+
+
+ + + + + + Page + + ${(table.state.pagination.pageIndex + 1).toLocaleString()} of + ${table.getPageCount().toLocaleString()} + + + + | Go to page: + + + +
+
${table.getRowModel().rows.length.toLocaleString()} Rows
+
+ +
+
${JSON.stringify(table.state, null, 2)}
+
+ + ` + } +} diff --git a/examples/lit/expanding/src/makeData.ts b/examples/lit/expanding/src/makeData.ts new file mode 100644 index 0000000000..d63c724b74 --- /dev/null +++ b/examples/lit/expanding/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/lit/expanding/tsconfig.json b/examples/lit/expanding/tsconfig.json new file mode 100644 index 0000000000..02eb5e4c87 --- /dev/null +++ b/examples/lit/expanding/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "emitDecoratorMetadata": true, + "noEmit": true, + "jsx": "react-jsx", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/lit/expanding/vite.config.js b/examples/lit/expanding/vite.config.js new file mode 100644 index 0000000000..fa3b238ac6 --- /dev/null +++ b/examples/lit/expanding/vite.config.js @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + ], +}) diff --git a/examples/lit/filters-faceted/index.html b/examples/lit/filters-faceted/index.html new file mode 100644 index 0000000000..3238ed5dba --- /dev/null +++ b/examples/lit/filters-faceted/index.html @@ -0,0 +1,13 @@ + + + + + + TanStack Lit Table - Filters Faceted + + +
+ + + + diff --git a/examples/lit/filters-faceted/package.json b/examples/lit/filters-faceted/package.json new file mode 100644 index 0000000000..68fcebc77d --- /dev/null +++ b/examples/lit/filters-faceted/package.json @@ -0,0 +1,21 @@ +{ + "name": "tanstack-lit-table-example-filters-faceted", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "lit": "^3.3.2" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/lit/filters-faceted/src/main.ts b/examples/lit/filters-faceted/src/main.ts new file mode 100644 index 0000000000..7db26f7f7b --- /dev/null +++ b/examples/lit/filters-faceted/src/main.ts @@ -0,0 +1,599 @@ +import { customElement, property, state } from 'lit/decorators.js' +import { LitElement, html } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { + FlexRender, + TableController, + columnFacetingFeature, + columnFilteringFeature, + createFacetedMinMaxValues, + createFacetedRowModel, + createFacetedUniqueValues, + createFilteredRowModel, + createPaginatedRowModel, + filterFns, + globalFilteringFeature, + rowPaginationFeature, + tableFeatures, +} from '@tanstack/lit-table' +import { makeData } from './makeData' +import type { + CellData, + Column, + ColumnDef, + RowData, + Table, + TableFeatures, +} from '@tanstack/lit-table' +import type { Person } from './makeData' + +const _features = tableFeatures({ + columnFilteringFeature, + globalFilteringFeature, + columnFacetingFeature, + rowPaginationFeature, +}) + +const columns: Array> = [ + { + header: 'Name', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'firstName', + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => info.getValue(), + header: () => html`Last Name`, + footer: (props) => props.column.id, + }, + ], + }, + { + header: 'Info', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'age', + header: () => 'Age', + footer: (props) => props.column.id, + }, + { + header: 'More Info', + columns: [ + { + accessorKey: 'visits', + header: () => html`Visits`, + footer: (props) => props.column.id, + }, + { + accessorKey: 'status', + header: 'Status', + footer: (props) => props.column.id, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + footer: (props) => props.column.id, + }, + ], + }, + ], + }, +] + +@customElement('faceted-filter') +class FacetedFilter extends LitElement { + @property({ attribute: false }) + column!: Column + + @property({ attribute: false }) + table!: Table + + private _debounceTimer: ReturnType | undefined + + private _debouncedSetFilterValue(value: unknown) { + clearTimeout(this._debounceTimer) + this._debounceTimer = setTimeout(() => { + this.column.setFilterValue(value) + }, 500) + } + + render() { + const firstValue = this.table + .getPreFilteredRowModel() + .flatRows[0]?.getValue(this.column.id) + + const columnFilterValue = this.column.getFilterValue() + + if (typeof firstValue === 'number') { + const minMaxValues = this.column.getFacetedMinMaxValues() + return html` +
+ { + const val = (e.target as HTMLInputElement).value + this._debouncedSetFilterValue((old: [number, number]) => [ + val ? Number(val) : undefined, + old?.[1], + ]) + }} + placeholder=${`Min ${minMaxValues?.[0] !== undefined ? `(${minMaxValues[0]})` : ''}`} + style="width: 96px; border: 1px solid gray; border-radius: 4px; padding: 2px" + /> + { + const val = (e.target as HTMLInputElement).value + this._debouncedSetFilterValue((old: [number, number]) => [ + old?.[0], + val ? Number(val) : undefined, + ]) + }} + placeholder=${`Max ${minMaxValues?.[1] !== undefined ? `(${minMaxValues[1]})` : ''}`} + style="width: 96px; border: 1px solid gray; border-radius: 4px; padding: 2px" + /> +
+ ` + } + + const sortedUniqueValues = Array.from( + this.column.getFacetedUniqueValues().keys(), + ).sort() + + return html` +
+ + ${sortedUniqueValues + .slice(0, 5000) + .map((value: string) => html``)} + + { + const val = (e.target as HTMLInputElement).value + this._debouncedSetFilterValue(val) + }} + placeholder=${`Search... (${this.column.getFacetedUniqueValues().size})`} + style="width: 144px; border: 1px solid gray; border-radius: 4px; padding: 2px" + list=${this.column.id + 'list'} + /> +
+ ` + } +} + +@customElement('lit-table-example') +class LitTableExample extends LitElement { + @state() + private _data: Array = makeData(1_000) + + private tableController = new TableController(this) + + private _globalFilterDebounce: ReturnType | undefined + private _globalFilterValue = '' + + protected render() { + const table = this.tableController.table( + { + _features, + _rowModels: { + facetedRowModel: createFacetedRowModel(), + facetedMinMaxValues: createFacetedMinMaxValues(), + facetedUniqueValues: createFacetedUniqueValues(), + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + }, + data: this._data, + columns, + globalFilterFn: 'includesString', + debugTable: true, + debugHeaders: true, + debugColumns: false, + }, + (state) => ({ + columnFilters: state.columnFilters, + globalFilter: state.globalFilter, + pagination: state.pagination, + }), + ) + + return html` +
+
+ + +
+ { + const value = (e.target as HTMLInputElement).value + clearTimeout(this._globalFilterDebounce) + this._globalFilterDebounce = setTimeout(() => { + table.setGlobalFilter(value) + }, 500) + }} + placeholder="Search all columns..." + /> +
+ + + ${repeat( + table.getHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` + + ${headerGroup.headers.map( + (header) => html` + + `, + )} + + `, + )} + + + ${table.getRowModel().rows.map( + (row) => html` + + ${row + .getAllCells() + .map((cell) => html` `)} + + `, + )} + +
+ ${header.isPlaceholder + ? null + : html` + ${FlexRender({ header })} + ${header.column.getCanFilter() + ? html`
+ +
` + : null} + `} +
${FlexRender({ cell })}
+
+ + + + + +
Page
+ + ${(table.state.pagination.pageIndex + 1).toLocaleString()} of + ${table.getPageCount().toLocaleString()} + +
+ + | Go to page: + + + +
+
+ Showing ${table.getRowModel().rows.length.toLocaleString()} of + ${table.getPrePaginatedRowModel().rows.length.toLocaleString()} Rows +
+
${JSON.stringify(table.state, null, 2)}
+
+ + ` + } +} diff --git a/examples/lit/filters-faceted/src/makeData.ts b/examples/lit/filters-faceted/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/lit/filters-faceted/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/lit/filters-faceted/tsconfig.json b/examples/lit/filters-faceted/tsconfig.json new file mode 100644 index 0000000000..02eb5e4c87 --- /dev/null +++ b/examples/lit/filters-faceted/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "emitDecoratorMetadata": true, + "noEmit": true, + "jsx": "react-jsx", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/lit/filters-faceted/vite.config.js b/examples/lit/filters-faceted/vite.config.js new file mode 100644 index 0000000000..fa3b238ac6 --- /dev/null +++ b/examples/lit/filters-faceted/vite.config.js @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + ], +}) diff --git a/examples/lit/filters-fuzzy/index.html b/examples/lit/filters-fuzzy/index.html new file mode 100644 index 0000000000..f2a51fb62f --- /dev/null +++ b/examples/lit/filters-fuzzy/index.html @@ -0,0 +1,13 @@ + + + + + + TanStack Lit Table - Filters Fuzzy + + +
+ + + + diff --git a/examples/lit/filters-fuzzy/package.json b/examples/lit/filters-fuzzy/package.json new file mode 100644 index 0000000000..96446efa40 --- /dev/null +++ b/examples/lit/filters-fuzzy/package.json @@ -0,0 +1,22 @@ +{ + "name": "tanstack-lit-table-example-filters-fuzzy", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "@tanstack/match-sorter-utils": "^9.0.0-alpha.4", + "lit": "^3.3.2" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/lit/filters-fuzzy/src/main.ts b/examples/lit/filters-fuzzy/src/main.ts new file mode 100644 index 0000000000..367831565d --- /dev/null +++ b/examples/lit/filters-fuzzy/src/main.ts @@ -0,0 +1,577 @@ +import { customElement, property, state } from 'lit/decorators.js' +import { LitElement, html } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { + FlexRender, + TableController, + columnFilteringFeature, + createColumnHelper, + createFilteredRowModel, + createPaginatedRowModel, + createSortedRowModel, + filterFns, + globalFilteringFeature, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/lit-table' +import { compareItems, rankItem } from '@tanstack/match-sorter-utils' +import { makeData } from './makeData' +import type { Column, FilterFn, SortFn } from '@tanstack/lit-table' +import type { RankingInfo } from '@tanstack/match-sorter-utils' +import type { Person } from './makeData' + +const _features = tableFeatures({ + columnFilteringFeature, + globalFilteringFeature, + rowSortingFeature, + rowPaginationFeature, +}) + +const columnHelper = createColumnHelper() + +const fuzzyFilter: FilterFn = ( + row, + columnId, + value, + addMeta, +) => { + const itemRank = rankItem(row.getValue(columnId), value) + addMeta?.({ itemRank }) + return itemRank.passed +} + +const fuzzySort: SortFn = (rowA, rowB, columnId) => { + let dir = 0 + if (rowA.columnFiltersMeta[columnId]) { + dir = compareItems( + rowA.columnFiltersMeta[columnId].itemRank!, + rowB.columnFiltersMeta[columnId].itemRank!, + ) + } + return dir === 0 ? sortFns.alphanumeric(rowA, rowB, columnId) : dir +} + +declare module '@tanstack/lit-table' { + interface FilterFns { + fuzzy: FilterFn + } + interface FilterMeta { + itemRank?: RankingInfo + } +} + +const columns = columnHelper.columns([ + columnHelper.accessor('id', { + filterFn: 'equalsString', + }), + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + filterFn: 'includesStringSensitive', + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => html`Last Name`, + filterFn: 'includesString', + }), + columnHelper.accessor((row) => `${row.firstName} ${row.lastName}`, { + id: 'fullName', + header: 'Full Name', + cell: (info) => info.getValue(), + filterFn: 'fuzzy', + sortFn: fuzzySort, + }), +]) + +@customElement('debounced-input') +class DebouncedInput extends LitElement { + @property() + value: string | number = '' + + @property() + type = 'text' + + @property() + placeholder = '' + + @property({ type: Number }) + debounce = 500 + + private _timer: ReturnType | undefined + + render() { + return html` + { + const val = (e.target as HTMLInputElement).value + clearTimeout(this._timer) + this._timer = setTimeout(() => { + this.dispatchEvent( + new CustomEvent('debounced-change', { + detail: { value: val }, + bubbles: true, + composed: true, + }), + ) + }, this.debounce) + }} + placeholder=${this.placeholder} + style="border: 1px solid gray; border-radius: 4px; padding: 2px" + /> + ` + } +} + +@customElement('column-filter') +class ColumnFilterEl extends LitElement { + @property({ attribute: false }) + column!: Column + + render() { + const columnFilterValue = this.column.getFilterValue() + + return html` + + this.column.setFilterValue(e.detail.value)} + placeholder="Search..." + > + ` + } +} + +@customElement('lit-table-example') +class LitTableExample extends LitElement { + @state() + private _data: Array = makeData(5_000) + + private tableController = new TableController(this) + + protected render() { + const table = this.tableController.table( + { + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel({ + ...filterFns, + fuzzy: fuzzyFilter, + }), + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, + columns, + data: this._data, + globalFilterFn: 'fuzzy', + debugTable: true, + debugHeaders: true, + debugColumns: false, + }, + (state) => ({ + columnFilters: state.columnFilters, + globalFilter: state.globalFilter, + sorting: state.sorting, + pagination: state.pagination, + }), + ) + + // Auto-sort by fullName when its filter is active + if (table.state.columnFilters[0]?.id === 'fullName') { + if (table.state.sorting[0]?.id !== 'fullName') { + table.setSorting([{ id: 'fullName', desc: false }]) + } + } + + return html` +
+
+ + +
+
+ + table.setGlobalFilter(String(e.detail.value))} + placeholder="Search all columns..." + > +
+
+ + + ${repeat( + table.getHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` + + ${headerGroup.headers.map( + (header) => html` + + `, + )} + + `, + )} + + + ${table.getRowModel().rows.map( + (row) => html` + + ${row + .getAllCells() + .map((cell) => html` `)} + + `, + )} + +
+ ${header.isPlaceholder + ? null + : html` +
+ ${FlexRender({ header })} + ${{ asc: ' 🔼', desc: ' 🔽' }[ + header.column.getIsSorted() as string + ] ?? null} +
+ ${header.column.getCanFilter() + ? html`
+ +
` + : null} + `} +
${FlexRender({ cell })}
+
+
+ + + + + + Page + + ${(table.state.pagination.pageIndex + 1).toLocaleString()} of + ${table.getPageCount().toLocaleString()} + + + + | Go to page: + { + const page = (e.target as HTMLInputElement).value + ? Number((e.target as HTMLInputElement).value) - 1 + : 0 + table.setPageIndex(page) + }} + style="width: 64px; border: 1px solid gray; padding: 2px; border-radius: 4px" + /> + + +
+
+ ${table.getPrePaginatedRowModel().rows.length.toLocaleString()} Rows +
+
${JSON.stringify(table.state, null, 2)}
+
+ + ` + } +} diff --git a/examples/lit/filters-fuzzy/src/makeData.ts b/examples/lit/filters-fuzzy/src/makeData.ts new file mode 100644 index 0000000000..38c1db1f15 --- /dev/null +++ b/examples/lit/filters-fuzzy/src/makeData.ts @@ -0,0 +1,45 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + id: number + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (num: number): Person => ({ + id: num, + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (index): Person => ({ + ...newPerson(index), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/lit/filters-fuzzy/tsconfig.json b/examples/lit/filters-fuzzy/tsconfig.json new file mode 100644 index 0000000000..02eb5e4c87 --- /dev/null +++ b/examples/lit/filters-fuzzy/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "emitDecoratorMetadata": true, + "noEmit": true, + "jsx": "react-jsx", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/lit/filters-fuzzy/vite.config.js b/examples/lit/filters-fuzzy/vite.config.js new file mode 100644 index 0000000000..fa3b238ac6 --- /dev/null +++ b/examples/lit/filters-fuzzy/vite.config.js @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + ], +}) diff --git a/examples/lit/filters/index.html b/examples/lit/filters/index.html index 9807874fae..15b0437b92 100644 --- a/examples/lit/filters/index.html +++ b/examples/lit/filters/index.html @@ -4,7 +4,6 @@ Vite App -
diff --git a/examples/lit/filters/package.json b/examples/lit/filters/package.json index e221474fbf..0a1c92c236 100644 --- a/examples/lit/filters/package.json +++ b/examples/lit/filters/package.json @@ -1,21 +1,21 @@ { "name": "tanstack-lit-table-example-filters", - "version": "0.0.0", "private": true, "scripts": { "dev": "vite", "build": "vite build", "serve": "vite preview", - "start": "vite" + "start": "vite", + "lint": "eslint ./src" }, "dependencies": { - "@faker-js/faker": "^8.4.1", - "@tanstack/lit-table": "^8.20.5", - "lit": "^3.1.4" + "@faker-js/faker": "^10.4.0", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "lit": "^3.3.2" }, "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/lit/filters/src/main.ts b/examples/lit/filters/src/main.ts index 3cb1695684..2568df9401 100644 --- a/examples/lit/filters/src/main.ts +++ b/examples/lit/filters/src/main.ts @@ -1,36 +1,47 @@ import { customElement, property, state } from 'lit/decorators.js' -import { html, LitElement } from 'lit' +import { LitElement, html } from 'lit' import { repeat } from 'lit/directives/repeat.js' import { + FlexRender, + TableController, + columnFilteringFeature, + createFilteredRowModel, + createPaginatedRowModel, + filterFns, + rowPaginationFeature, + tableFeatures, +} from '@tanstack/lit-table' +import { makeData } from './makeData' +import type { + CellData, Column, ColumnDef, - ColumnFiltersState, - flexRender, - getCoreRowModel, - getFilteredRowModel, - getPaginationRowModel, - getSortedRowModel, RowData, - TableController, + TableFeatures, } from '@tanstack/lit-table' -import { makeData, Person } from './makeData' +import type { Person } from './makeData' -const columns: ColumnDef[] = [ +const _features = tableFeatures({ + columnFilteringFeature, + rowPaginationFeature, +}) + +const columns: Array> = [ { accessorKey: 'firstName', - cell: info => info.getValue(), + cell: (info) => info.getValue(), }, { - accessorFn: row => row.lastName, + accessorFn: (row) => row.lastName, id: 'lastName', - cell: info => info.getValue(), + cell: (info) => info.getValue(), header: () => html`Last Name`, }, { - accessorFn: row => `${row.firstName} ${row.lastName}`, + accessorFn: (row) => `${row.firstName} ${row.lastName}`, id: 'fullName', header: 'Full Name', - cell: info => info.getValue(), + cell: (info) => info.getValue(), }, { accessorKey: 'age', @@ -63,21 +74,23 @@ const columns: ColumnDef[] = [ ] declare module '@tanstack/lit-table' { - //allows us to define custom properties for our columns - interface ColumnMeta { + // allows us to define custom properties for our columns + interface ColumnMeta< + TFeatures extends TableFeatures, + TData extends RowData, + TValue extends CellData = CellData, + > { filterVariant?: 'text' | 'range' | 'select' } } -const data = makeData(50_000) - @customElement('column-filter') class ColumnFilter extends LitElement { @property() - private column!: Column + private column!: Column private onChange(evt: InputEvent) { - this.column?.setFilterValue((evt.target as HTMLInputElement).value) + this.column.setFilterValue((evt.target as HTMLInputElement).value) } render() { @@ -104,9 +117,11 @@ class ColumnFilter extends LitElement { @change="${(e: Event) => this.column.setFilterValue((old: [number, number]) => [ parseInt((e.target as HTMLInputElement).value, 10), - old?.[1], + old[1], ])}" - value=${(columnFilterValue as [number, number])?.[0] ?? ''} + value=${( + columnFilterValue as [number, number] | undefined + )?.[0] ?? ''} />
` @@ -129,72 +146,63 @@ class ColumnFilter extends LitElement { @customElement('lit-table-example') class LitTableExample extends LitElement { - private tableController = new TableController(this) - @state() - private _columnFilters: ColumnFiltersState = [] + private _data: Array = makeData(50_000) - protected render(): unknown { - const table = this.tableController.table({ - data, - columns, - filterFns: {}, - state: { - columnFilters: this._columnFilters, - }, - onColumnFiltersChange: updater => { - if (typeof updater === 'function') { - this._columnFilters = updater(this._columnFilters) - } else { - this._columnFilters = updater - } + private tableController = new TableController(this) + + protected render() { + const table = this.tableController.table( + { + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + }, + data: this._data, + columns, + debugTable: true, + debugHeaders: true, + debugColumns: false, }, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), //client side filtering - getSortedRowModel: getSortedRowModel(), - getPaginationRowModel: getPaginationRowModel(), - debugTable: true, - debugHeaders: true, - debugColumns: false, - }) + (state) => ({ + columnFilters: state.columnFilters, + pagination: state.pagination, + }), + ) return html` +
+ + +
${repeat( table.getHeaderGroups(), - headerGroup => headerGroup.id, - headerGroup => html` + (headerGroup) => headerGroup.id, + (headerGroup) => html` ${repeat( headerGroup.headers, - header => header.id, - header => html` + (header) => header.id, + (header) => html` - ` + `, )} - ` + `, )} - ${table - .getRowModel() - .rows.slice(0, 10) - .map( - row => html` - - ${row - .getVisibleCells() - .map( - cell => html` - - ` - )} - - ` - )} + ${table.getRowModel().rows.map( + (row) => html` + + ${row + .getAllCells() + .map((cell) => html` `)} + + `, + )}
${header.isPlaceholder ? null - : html`
- ${flexRender( - header.column.columnDef.header, - header.getContext() - )} - ${{ asc: ' 🔼', desc: ' 🔽' }[ - header.column.getIsSorted() as string - ] ?? null} -
+ : html`
${FlexRender({ header })}
${header.column.getCanFilter() ? html`
` : null} `}
- ${flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} -
${FlexRender({ cell })}
@@ -261,12 +257,12 @@ class LitTableExample extends LitElement { Page - ${table.getState().pagination.pageIndex + 1} of - ${table.getPageCount()} + ${(table.state.pagination.pageIndex + 1).toLocaleString()} of + ${table.getPageCount().toLocaleString()}
-
${JSON.stringify(this._columnFilters, null, 2)}
+
${JSON.stringify(table.state.columnFilters, null, 2)}
` } diff --git a/examples/lit/filters/src/makeData.ts b/examples/lit/filters/src/makeData.ts index 1a2576c988..6311127267 100644 --- a/examples/lit/filters/src/makeData.ts +++ b/examples/lit/filters/src/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,13 +29,13 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] return range(len).map((): Person => { return { ...newPerson(), diff --git a/examples/lit/filters/tsconfig.json b/examples/lit/filters/tsconfig.json index 9ad885e68b..d25d9f8344 100644 --- a/examples/lit/filters/tsconfig.json +++ b/examples/lit/filters/tsconfig.json @@ -4,8 +4,6 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, @@ -14,12 +12,11 @@ "jsx": "react-jsx", "experimentalDecorators": true, "useDefineForClassFields": false, - - /* Linting */ "strict": true, "noUnusedLocals": false, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, - "include": ["src"] + "include": ["src", "vite.config.js", "vite.config.ts"] } diff --git a/examples/lit/grouping/index.html b/examples/lit/grouping/index.html new file mode 100644 index 0000000000..696c85410d --- /dev/null +++ b/examples/lit/grouping/index.html @@ -0,0 +1,13 @@ + + + + + + TanStack Lit Table - Grouping + + +
+ + + + diff --git a/examples/lit/grouping/package.json b/examples/lit/grouping/package.json new file mode 100644 index 0000000000..7636794b08 --- /dev/null +++ b/examples/lit/grouping/package.json @@ -0,0 +1,21 @@ +{ + "name": "tanstack-lit-table-example-grouping", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "lit": "^3.3.2" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/lit/grouping/src/main.ts b/examples/lit/grouping/src/main.ts new file mode 100644 index 0000000000..9903bf40fa --- /dev/null +++ b/examples/lit/grouping/src/main.ts @@ -0,0 +1,502 @@ +import { customElement, state } from 'lit/decorators.js' +import { LitElement, html } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { + FlexRender, + TableController, + aggregationFns, + columnFilteringFeature, + columnGroupingFeature, + createColumnHelper, + createExpandedRowModel, + createFilteredRowModel, + createGroupedRowModel, + createPaginatedRowModel, + createSortedRowModel, + filterFns, + rowExpandingFeature, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/lit-table' +import { makeData } from './makeData' +import type { ColumnDef } from '@tanstack/lit-table' +import type { Person } from './makeData' + +const _features = tableFeatures({ + columnFilteringFeature, + columnGroupingFeature, + rowExpandingFeature, + rowPaginationFeature, + rowSortingFeature, +}) + +const columnHelper = createColumnHelper() + +const columns: Array> = + columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + cell: (info) => info.getValue(), + getGroupingValue: (row) => `${row.firstName} ${row.lastName}`, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + header: () => html`Last Name`, + cell: (info) => info.getValue(), + }), + columnHelper.accessor('age', { + header: () => 'Age', + aggregatedCell: ({ getValue }) => + Math.round(getValue() * 100) / 100, + aggregationFn: 'median', + }), + columnHelper.accessor('visits', { + header: () => html`Visits`, + aggregationFn: 'sum', + aggregatedCell: ({ getValue }) => getValue().toLocaleString(), + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + cell: ({ getValue }) => Math.round(getValue() * 100) / 100 + '%', + aggregationFn: 'mean', + aggregatedCell: ({ getValue }) => + Math.round(getValue() * 100) / 100 + '%', + }), + ]) + +@customElement('lit-table-example') +class LitTableExample extends LitElement { + @state() + private _data: Array = makeData(10_000) + + private tableController = new TableController(this) + + protected render() { + const table = this.tableController.table( + { + _features, + _rowModels: { + expandedRowModel: createExpandedRowModel(), + filteredRowModel: createFilteredRowModel(filterFns), + groupedRowModel: createGroupedRowModel(aggregationFns), + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, + columns, + data: this._data, + debugTable: true, + }, + (state) => ({ + grouping: state.grouping, + expanded: state.expanded, + pagination: state.pagination, + }), + ) + + return html` +
+
+ + +
+
+ + + ${repeat( + table.getHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` + + ${headerGroup.headers.map( + (header) => html` + + `, + )} + + `, + )} + + + ${table.getRowModel().rows.map( + (row) => html` + + ${row + .getAllCells() + .map( + (cell) => html` + + `, + )} + + `, + )} + +
+ ${header.isPlaceholder + ? null + : html`
+ ${header.column.getCanGroup() + ? html`` + : null} + ${FlexRender({ header })} +
`} +
+ ${cell.getIsGrouped() + ? html`` + : cell.getIsAggregated() + ? FlexRender({ cell }) + : cell.getIsPlaceholder() + ? null + : FlexRender({ cell })} +
+
+
+ + + + + + Page + + ${(table.state.pagination.pageIndex + 1).toLocaleString()} of + ${table.getPageCount().toLocaleString()} + + + + | Go to page: + { + const page = (e.target as HTMLInputElement).value + ? Number((e.target as HTMLInputElement).value) - 1 + : 0 + table.setPageIndex(page) + }} + style="width: 64px; border: 1px solid gray; padding: 2px; border-radius: 4px" + /> + + +
+
${table.getRowModel().rows.length.toLocaleString()} Rows
+
${JSON.stringify(table.state, null, 2)}
+
+ + ` + } +} diff --git a/examples/lit/grouping/src/makeData.ts b/examples/lit/grouping/src/makeData.ts new file mode 100644 index 0000000000..d63c724b74 --- /dev/null +++ b/examples/lit/grouping/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/lit/grouping/tsconfig.json b/examples/lit/grouping/tsconfig.json new file mode 100644 index 0000000000..02eb5e4c87 --- /dev/null +++ b/examples/lit/grouping/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "emitDecoratorMetadata": true, + "noEmit": true, + "jsx": "react-jsx", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/lit/grouping/vite.config.js b/examples/lit/grouping/vite.config.js new file mode 100644 index 0000000000..fa3b238ac6 --- /dev/null +++ b/examples/lit/grouping/vite.config.js @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + ], +}) diff --git a/examples/lit/pagination/index.html b/examples/lit/pagination/index.html new file mode 100644 index 0000000000..31a3530bcb --- /dev/null +++ b/examples/lit/pagination/index.html @@ -0,0 +1,13 @@ + + + + + + TanStack Lit Table - Pagination + + +
+ + + + diff --git a/examples/lit/pagination/package.json b/examples/lit/pagination/package.json new file mode 100644 index 0000000000..38b6dfed09 --- /dev/null +++ b/examples/lit/pagination/package.json @@ -0,0 +1,21 @@ +{ + "name": "tanstack-lit-table-example-pagination", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "lit": "^3.3.2" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/lit/pagination/src/main.ts b/examples/lit/pagination/src/main.ts new file mode 100644 index 0000000000..da19d260f6 --- /dev/null +++ b/examples/lit/pagination/src/main.ts @@ -0,0 +1,438 @@ +import { customElement, state } from 'lit/decorators.js' +import { LitElement, html } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { + FlexRender, + TableController, + createColumnHelper, + createPaginatedRowModel, + rowPaginationFeature, + tableFeatures, +} from '@tanstack/lit-table' +import { makeData } from './makeData' +import type { Person } from './makeData' + +const _features = tableFeatures({ + rowPaginationFeature, +}) + +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => html`Last Name`, + footer: (props) => props.column.id, + }), + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + }), + columnHelper.accessor('visits', { + header: () => html`Visits`, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), +]) + +@customElement('lit-table-example') +class LitTableExample extends LitElement { + @state() + private _data: Array = makeData(1_000) + + private tableController = new TableController(this) + + protected render() { + const table = this.tableController.table( + { + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data: this._data, + debugTable: true, + }, + (state) => ({ pagination: state.pagination }), + ) + + return html` +
+
+ + +
+
+ + + ${repeat( + table.getHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` + + ${headerGroup.headers.map( + (header) => html` + + `, + )} + + `, + )} + + + ${table.getRowModel().rows.map( + (row) => html` + + ${row + .getAllCells() + .map((cell) => html` `)} + + `, + )} + +
+ ${header.isPlaceholder + ? null + : html`
${FlexRender({ header })}
`} +
${FlexRender({ cell })}
+
+
+ + + + + +
Page
+ + ${(table.state.pagination.pageIndex + 1).toLocaleString()} of + ${table.getPageCount().toLocaleString()} + +
+ + | Go to page: + + + +
+
+ Showing ${table.getRowModel().rows.length.toLocaleString()} of + ${table.getRowCount().toLocaleString()} Rows +
+
${JSON.stringify(table.state, null, 2)}
+
+ + ` + } +} diff --git a/examples/lit/pagination/src/makeData.ts b/examples/lit/pagination/src/makeData.ts new file mode 100644 index 0000000000..d274a17f45 --- /dev/null +++ b/examples/lit/pagination/src/makeData.ts @@ -0,0 +1,47 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + return makeDataLevel() +} diff --git a/examples/lit/pagination/tsconfig.json b/examples/lit/pagination/tsconfig.json new file mode 100644 index 0000000000..02eb5e4c87 --- /dev/null +++ b/examples/lit/pagination/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "emitDecoratorMetadata": true, + "noEmit": true, + "jsx": "react-jsx", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/lit/pagination/vite.config.js b/examples/lit/pagination/vite.config.js new file mode 100644 index 0000000000..75dafbd933 --- /dev/null +++ b/examples/lit/pagination/vite.config.js @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite' +import rollupReplace from '@rollup/plugin-replace' + +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + ], +}) diff --git a/examples/lit/row-pinning/index.html b/examples/lit/row-pinning/index.html new file mode 100644 index 0000000000..55114de6a6 --- /dev/null +++ b/examples/lit/row-pinning/index.html @@ -0,0 +1,13 @@ + + + + + + TanStack Lit Table - Row Pinning + + +
+ + + + diff --git a/examples/lit/row-pinning/package.json b/examples/lit/row-pinning/package.json new file mode 100644 index 0000000000..08ac73082e --- /dev/null +++ b/examples/lit/row-pinning/package.json @@ -0,0 +1,21 @@ +{ + "name": "tanstack-lit-table-example-row-pinning", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "lit": "^3.3.2" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/lit/row-pinning/src/main.ts b/examples/lit/row-pinning/src/main.ts new file mode 100644 index 0000000000..4e0b85bf3e --- /dev/null +++ b/examples/lit/row-pinning/src/main.ts @@ -0,0 +1,586 @@ +import { customElement, state } from 'lit/decorators.js' +import { LitElement, html } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { + FlexRender, + TableController, + columnFilteringFeature, + createExpandedRowModel, + createFilteredRowModel, + createPaginatedRowModel, + filterFns, + rowExpandingFeature, + rowPaginationFeature, + rowPinningFeature, + tableFeatures, +} from '@tanstack/lit-table' +import { makeData } from './makeData' +import type { Column, ColumnDef, Row, Table } from '@tanstack/lit-table' +import type { Person } from './makeData' + +const _features = tableFeatures({ + rowPinningFeature, + rowExpandingFeature, + columnFilteringFeature, + rowPaginationFeature, +}) + +function renderFilter( + column: Column, + table: Table, +) { + const firstValue = table + .getPreFilteredRowModel() + .flatRows[0]?.getValue(column.id) + + const columnFilterValue = column.getFilterValue() + + if (typeof firstValue === 'number') { + return html` +
+ + +
+ ` + } + + return html` + + ` +} + +function renderPinnedRow( + row: Row, + table: Table, +) { + const isPinnedTop = row.getIsPinned() === 'top' + const topOffset = isPinnedTop + ? `${row.getPinnedIndex() * 26 + 48}px` + : undefined + const bottomOffset = !isPinnedTop + ? `${(table.getBottomRows().length - 1 - row.getPinnedIndex()) * 26}px` + : undefined + + return html` + + ${row + .getAllCells() + .map((cell) => html` ${FlexRender({ cell })} `)} + + ` +} + +const columns: Array> = [ + { + id: 'pin', + header: () => 'Pin', + cell: ({ row }) => + row.getIsPinned() + ? html`` + : html`
+ + +
`, + }, + { + accessorKey: 'firstName', + header: ({ table }) => html` + + First Name + `, + cell: ({ row, getValue }) => html` +
+ ${row.getCanExpand() + ? html`` + : '🔵'} + ${getValue()} +
+ `, + footer: (props) => props.column.id, + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => info.getValue(), + header: () => html`Last Name`, + }, + { + accessorKey: 'age', + header: () => 'Age', + }, + { + accessorKey: 'visits', + header: () => html`Visits`, + }, + { + accessorKey: 'status', + header: 'Status', + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + }, +] + +@customElement('lit-table-example') +class LitTableExample extends LitElement { + @state() + private _data: Array = makeData(1_000, 2, 2) + + private tableController = new TableController(this) + + protected render() { + const table = this.tableController.table( + { + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + expandedRowModel: createExpandedRowModel(), + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data: this._data, + initialState: { + pagination: { pageSize: 20, pageIndex: 0 }, + }, + getSubRows: (row) => row.subRows, + keepPinnedRows: true, + debugAll: true, + }, + (state) => ({ + rowPinning: state.rowPinning, + expanded: state.expanded, + columnFilters: state.columnFilters, + pagination: state.pagination, + }), + ) + + return html` +
+
+
+ + +
+
+ + + ${repeat( + table.getHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` + + ${headerGroup.headers.map( + (header) => html` + + `, + )} + + `, + )} + + + ${table.getTopRows().map((row) => renderPinnedRow(row, table))} + ${table.getCenterRows().map( + (row) => html` + + ${row + .getAllCells() + .map((cell) => html` `)} + + `, + )} + ${table.getBottomRows().map((row) => renderPinnedRow(row, table))} + +
+ ${header.isPlaceholder + ? null + : html` +
+ ${FlexRender({ header })} + ${header.column.getCanFilter() + ? html`
+ ${renderFilter(header.column, table)} +
` + : null} +
+ `} +
${FlexRender({ cell })}
+
+ +
+
+ + + + + + Page + + ${(table.state.pagination.pageIndex + 1).toLocaleString()} of + ${table.getPageCount().toLocaleString()} + + + + | Go to page: + + + +
+
+
+${JSON.stringify(
+            {
+              rowPinning: table.state.rowPinning,
+              pagination: table.state.pagination,
+            },
+            null,
+            2,
+          )}
+
+ + ` + } +} diff --git a/examples/lit/row-pinning/src/makeData.ts b/examples/lit/row-pinning/src/makeData.ts new file mode 100644 index 0000000000..d63c724b74 --- /dev/null +++ b/examples/lit/row-pinning/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/lit/row-pinning/tsconfig.json b/examples/lit/row-pinning/tsconfig.json new file mode 100644 index 0000000000..02eb5e4c87 --- /dev/null +++ b/examples/lit/row-pinning/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "emitDecoratorMetadata": true, + "noEmit": true, + "jsx": "react-jsx", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/lit/row-pinning/vite.config.js b/examples/lit/row-pinning/vite.config.js new file mode 100644 index 0000000000..fa3b238ac6 --- /dev/null +++ b/examples/lit/row-pinning/vite.config.js @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + ], +}) diff --git a/examples/lit/row-selection/index.html b/examples/lit/row-selection/index.html index 9807874fae..15b0437b92 100644 --- a/examples/lit/row-selection/index.html +++ b/examples/lit/row-selection/index.html @@ -4,7 +4,6 @@ Vite App -
diff --git a/examples/lit/row-selection/package.json b/examples/lit/row-selection/package.json index 27795be3dd..5d53c5fb4a 100644 --- a/examples/lit/row-selection/package.json +++ b/examples/lit/row-selection/package.json @@ -1,21 +1,21 @@ { "name": "tanstack-lit-table-example-row-selection", - "version": "0.0.0", "private": true, "scripts": { "dev": "vite", "build": "vite build", "serve": "vite preview", - "start": "vite" + "start": "vite", + "lint": "eslint ./src" }, "dependencies": { - "@faker-js/faker": "^8.4.1", - "@tanstack/lit-table": "^8.20.5", - "lit": "^3.1.4" + "@faker-js/faker": "^10.4.0", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "lit": "^3.3.2" }, "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/lit/row-selection/src/main.ts b/examples/lit/row-selection/src/main.ts index 9b146fe7f0..3f15b284c5 100644 --- a/examples/lit/row-selection/src/main.ts +++ b/examples/lit/row-selection/src/main.ts @@ -1,17 +1,28 @@ -import { customElement, property, state } from 'lit/decorators.js' -import { html, LitElement } from 'lit' +import { customElement, state } from 'lit/decorators.js' +import { LitElement, html } from 'lit' import { repeat } from 'lit/directives/repeat.js' import { - ColumnDef, - flexRender, - getCoreRowModel, - getFilteredRowModel, - getPaginationRowModel, + FlexRender, TableController, + columnFilteringFeature, + createFilteredRowModel, + createPaginatedRowModel, + filterFns, + rowPaginationFeature, + rowSelectionFeature, + tableFeatures, } from '@tanstack/lit-table' -import { makeData, Person } from './makeData' +import { makeData } from './makeData' +import type { ColumnDef } from '@tanstack/lit-table' +import type { Person } from './makeData' -const columns: ColumnDef[] = [ +const _features = tableFeatures({ + rowSelectionFeature, + columnFilteringFeature, + rowPaginationFeature, +}) + +const columns: Array> = [ { id: 'select', header: ({ table }) => html` @@ -34,19 +45,19 @@ const columns: ColumnDef[] = [ }, { accessorKey: 'firstName', - cell: info => info.getValue(), + cell: (info) => info.getValue(), }, { - accessorFn: row => row.lastName, + accessorFn: (row) => row.lastName, id: 'lastName', - cell: info => info.getValue(), + cell: (info) => info.getValue(), header: () => html`Last Name`, }, { - accessorFn: row => `${row.firstName} ${row.lastName}`, + accessorFn: (row) => `${row.firstName} ${row.lastName}`, id: 'fullName', header: 'Full Name', - cell: info => info.getValue(), + cell: (info) => info.getValue(), }, { accessorKey: 'age', @@ -66,63 +77,69 @@ const columns: ColumnDef[] = [ }, ] -const data = makeData(50_000) - @customElement('lit-table-example') class LitTableExample extends LitElement { - private tableController = new TableController(this) - @state() - private _rowSelection: Record = {} + private _data: Array = makeData(50_000) - protected render(): unknown { - const table = this.tableController.table({ - data, - columns, - filterFns: {}, - state: { - rowSelection: this._rowSelection, - }, - enableRowSelection: true, - onRowSelectionChange: updaterOrValue => { - if (typeof updaterOrValue === 'function') { - this._rowSelection = updaterOrValue(this._rowSelection) - } else { - this._rowSelection = updaterOrValue - } + private tableController = new TableController(this) + + protected render() { + const table = this.tableController.table( + { + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + }, + data: this._data, + columns, + enableRowSelection: true, + debugTable: true, }, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getPaginationRowModel: getPaginationRowModel(), - debugTable: true, - }) + (state) => ({ + rowSelection: state.rowSelection, + pagination: state.pagination, + }), + ) return html` +
+ + +
${repeat( table.getHeaderGroups(), - headerGroup => headerGroup.id, - headerGroup => html` + (headerGroup) => headerGroup.id, + (headerGroup) => html` ${repeat( headerGroup.headers, - header => header.id, - header => html` + (header) => header.id, + (header) => html` - ` + `, )} - ` + `, )} @@ -130,22 +147,13 @@ class LitTableExample extends LitElement { .getRowModel() .rows.slice(0, 10) .map( - row => html` + (row) => html` ${row - .getVisibleCells() - .map( - cell => html` - - ` - )} + .getAllCells() + .map((cell) => html` `)} - ` + `, )}
${header.isPlaceholder ? null - : html`
- ${flexRender( - header.column.columnDef.header, - header.getContext() - )} -
`} + : html`
${FlexRender({ header })}
`}
- ${flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - ${FlexRender({ cell })}
@@ -177,8 +185,8 @@ class LitTableExample extends LitElement { Page - ${table.getState().pagination.pageIndex + 1} of - ${table.getPageCount()} + ${(table.state.pagination.pageIndex + 1).toLocaleString()} of + ${table.getPageCount().toLocaleString()}
@@ -191,7 +199,6 @@ class LitTableExample extends LitElement { table { border: 1px solid lightgray; - border-collapse: collapse; } tbody { @@ -217,6 +224,210 @@ class LitTableExample extends LitElement { gap: 10px; padding: 4px 0; } + + /* Demo layout helpers for the plain example UI. */ + .demo-root { + padding: 0.5rem; + } + .spacer-xs { + height: 0.25rem; + } + .spacer-sm { + height: 0.5rem; + } + .spacer-md { + height: 1rem; + } + .controls, + .button-row, + .inline-controls, + .pin-actions, + .filter-row, + .form-actions { + display: flex; + align-items: center; + } + .button-row { + flex-wrap: wrap; + gap: 0.5rem; + } + .controls { + gap: 0.5rem; + } + .inline-controls, + .pin-actions { + gap: 0.25rem; + } + .pin-actions { + justify-content: center; + } + .filter-row { + gap: 0.5rem; + } + .form-actions { + gap: 1rem; + margin-bottom: 1rem; + } + .split-tables { + display: flex; + gap: 1rem; + } + .table-row-group { + display: flex; + } + .split-gap { + gap: 1rem; + } + .vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; + } + .column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + } + .column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; + } + .column-toggle-row, + .selection-cell { + padding: 0 0.25rem; + } + .selection-cell { + display: block; + } + .demo-button, + .pin-button, + .compact-input, + .filter-input, + .filter-select, + .page-size-input, + .text-input, + .number-input, + .wide-action-button, + .primary-action, + .secondary-action, + .success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; + } + .demo-button { + padding: 0.5rem; + } + .demo-button-sm { + padding: 0.25rem; + } + .demo-button-spaced { + margin-bottom: 0.5rem; + } + .pin-button { + padding: 0 0.5rem; + } + .outlined-table { + border: 2px solid #000; + } + .outlined-control { + border-color: #000; + } + .nowrap { + white-space: nowrap; + } + .demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; + } + .section-title { + font-size: 1.25rem; + } + .scroll-container { + overflow-x: auto; + } + .page-size-input { + width: 4rem; + padding: 0.25rem; + } + .number-input { + width: 5rem; + padding: 0 0.25rem; + } + .filter-input, + .filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + } + .filter-select { + width: 9rem; + } + .text-input { + width: 100%; + padding: 0 0.25rem; + } + .compact-input { + padding: 0 0.25rem; + } + .wide-action-button { + width: 16rem; + } + .summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; + } + .sortable-header, + .sortable { + cursor: pointer; + user-select: none; + } + .primary-action, + .success-action, + .secondary-action { + color: #fff; + } + .primary-action { + background: #3b82f6; + } + .success-action { + background: #22c55e; + } + .secondary-action { + background: #6b7280; + } + .submit-button:disabled { + opacity: 0.5; + } + .error-text { + color: #ef4444; + font-size: 0.75rem; + } + .success-text { + color: #16a34a; + } + .warning-text { + color: #ca8a04; + } + .muted-text { + color: #9ca3af; + } + .label-offset { + margin-left: 0.5rem; + } + .cell-padding { + padding: 0.25rem; + } + .table-spacer { + margin-bottom: 0.5rem; + } + .centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; + } ` } diff --git a/examples/lit/row-selection/src/makeData.ts b/examples/lit/row-selection/src/makeData.ts index d6c0639b22..fc070cd5d2 100644 --- a/examples/lit/row-selection/src/makeData.ts +++ b/examples/lit/row-selection/src/makeData.ts @@ -9,11 +9,11 @@ export type Person = { status: 'relationship' | 'complicated' | 'single' rank: number createdAt: Date - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -32,14 +32,14 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], rank: faker.number.int(100), } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] return range(len).map((_d): Person => { return { ...newPerson(), diff --git a/examples/lit/row-selection/tsconfig.json b/examples/lit/row-selection/tsconfig.json index 056b1603c8..1bbf4f9ece 100644 --- a/examples/lit/row-selection/tsconfig.json +++ b/examples/lit/row-selection/tsconfig.json @@ -4,8 +4,6 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, @@ -13,12 +11,11 @@ "noEmit": true, "experimentalDecorators": true, "useDefineForClassFields": false, - - /* Linting */ "strict": true, "noUnusedLocals": false, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, - "include": ["src"] + "include": ["src", "vite.config.js", "vite.config.ts"] } diff --git a/examples/lit/basic/.gitignore b/examples/lit/sorting-dynamic-data/.gitignore similarity index 100% rename from examples/lit/basic/.gitignore rename to examples/lit/sorting-dynamic-data/.gitignore diff --git a/examples/lit/basic/README.md b/examples/lit/sorting-dynamic-data/README.md similarity index 100% rename from examples/lit/basic/README.md rename to examples/lit/sorting-dynamic-data/README.md diff --git a/examples/lit/sorting-dynamic-data/index.html b/examples/lit/sorting-dynamic-data/index.html new file mode 100644 index 0000000000..15b0437b92 --- /dev/null +++ b/examples/lit/sorting-dynamic-data/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + +
+ + + + diff --git a/examples/lit/sorting-dynamic-data/package.json b/examples/lit/sorting-dynamic-data/package.json new file mode 100644 index 0000000000..c982040404 --- /dev/null +++ b/examples/lit/sorting-dynamic-data/package.json @@ -0,0 +1,20 @@ +{ + "name": "tanstack-lit-table-example-sorting-dynamic-data", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "lit": "^3.3.2" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/lit/sorting-dynamic-data/src/main.ts b/examples/lit/sorting-dynamic-data/src/main.ts new file mode 100644 index 0000000000..ac9da3841d --- /dev/null +++ b/examples/lit/sorting-dynamic-data/src/main.ts @@ -0,0 +1,444 @@ +import { customElement } from 'lit/decorators.js' +import { LitElement, PropertyValueMap, html } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { state } from 'lit/decorators/state.js' +import { + ColumnDef, + FlexRender, + TableController, + createSortedRowModel, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/lit-table' +import { Person, makeData } from './makeData' +import type { SortFn } from '@tanstack/lit-table' + +const _features = tableFeatures({ + rowSortingFeature, +}) + +const sortStatusFn: SortFn = ( + rowA, + rowB, + _columnId, +) => { + const statusA = rowA.original.status + const statusB = rowB.original.status + const statusOrder = ['single', 'complicated', 'relationship'] + return statusOrder.indexOf(statusA) - statusOrder.indexOf(statusB) +} + +const columns: Array> = [ + { + accessorKey: 'firstName', + cell: (info) => info.getValue(), + // this column will sort in ascending order by default since it is a string column + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => info.getValue(), + header: () => html`Last Name`, + sortUndefined: 'last', // force undefined values to the end + sortDescFirst: false, // first sort order will be ascending (nullable values can mess up auto detection of sort order) + }, + { + accessorKey: 'age', + header: () => 'Age', + // this column will sort in descending order by default since it is a number column + }, + { + accessorKey: 'visits', + header: () => html`Visits`, + sortUndefined: 'last', // force undefined values to the end + }, + { + accessorKey: 'status', + header: 'Status', + sortFn: sortStatusFn, // use our custom sorting function for this enum column + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + // enableSorting: false, //disable sorting for this column + }, + { + accessorKey: 'rank', + header: 'Rank', + invertSorting: true, // invert the sorting order (golf score-like where smaller is better) + }, + { + accessorKey: 'createdAt', + header: 'Created At', + // sortingFn: 'datetime' //make sure table knows this is a datetime column (usually can detect if no null values) + }, +] + +const data: Array = makeData(1_000) + +@customElement('lit-table-example') +class LitTableExample extends LitElement { + @state() + private _multiplier = 1 + + @state() + private _data: Array = new Array() + + private tableController = new TableController(this) + + constructor() { + super() + this._data = [...data] + } + + protected willUpdate( + _changedProperties: PropertyValueMap | Map, + ): void { + super.willUpdate(_changedProperties) + if (_changedProperties.has('_multiplier')) { + const newData: Array = data.map((d) => { + const p: Person = { + ...d, + visits: d.visits ? d.visits * this._multiplier : undefined, + } + return p + }) + this._data.length = 0 + this._data = newData + } + } + protected render() { + const table = this.tableController.table( + { + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + }, + columns, + data: this._data, + }, + (state) => ({ sorting: state.sorting }), + ) + + return html` +
+ + +
+ + + + ${repeat( + table.getHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` + + ${headerGroup.headers.map( + (header) => html` + + `, + )} + + `, + )} + + + ${table + .getRowModel() + .rows.slice(0, 10) + .map( + (row) => html` + + ${row + .getAllCells() + .map((cell) => html` `)} + + `, + )} + +
+ ${header.isPlaceholder + ? null + : html`
+ ${FlexRender({ header })} + ${{ asc: ' 🔼', desc: ' 🔽' }[ + header.column.getIsSorted() as string + ] ?? null} +
`} +
${FlexRender({ cell })}
+
${JSON.stringify(table.state.sorting, null, 2)}
+ + ` + } +} diff --git a/examples/lit/sorting-dynamic-data/src/makeData.ts b/examples/lit/sorting-dynamic-data/src/makeData.ts new file mode 100644 index 0000000000..d6c0639b22 --- /dev/null +++ b/examples/lit/sorting-dynamic-data/src/makeData.ts @@ -0,0 +1,52 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string | undefined + age: number + visits: number | undefined + progress: number + status: 'relationship' | 'complicated' | 'single' + rank: number + createdAt: Date + subRows?: Person[] +} + +const range = (len: number) => { + const arr: number[] = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: Math.random() < 0.1 ? undefined : faker.person.lastName(), + age: faker.number.int(40), + visits: Math.random() < 0.1 ? undefined : faker.number.int(1000), + progress: faker.number.int(100), + createdAt: faker.date.anytime(), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0]!, + rank: faker.number.int(100), + } +} + +export function makeData(...lens: number[]) { + const makeDataLevel = (depth = 0): Person[] => { + const len = lens[depth]! + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/lit/sorting-dynamic-data/tsconfig.json b/examples/lit/sorting-dynamic-data/tsconfig.json new file mode 100644 index 0000000000..22d0d1157a --- /dev/null +++ b/examples/lit/sorting-dynamic-data/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "useDefineForClassFields": false, + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/lit/sorting-dynamic-data/vite.config.js b/examples/lit/sorting-dynamic-data/vite.config.js new file mode 100644 index 0000000000..fa3b238ac6 --- /dev/null +++ b/examples/lit/sorting-dynamic-data/vite.config.js @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + ], +}) diff --git a/examples/lit/sorting/index.html b/examples/lit/sorting/index.html index 9807874fae..15b0437b92 100644 --- a/examples/lit/sorting/index.html +++ b/examples/lit/sorting/index.html @@ -4,7 +4,6 @@ Vite App -
diff --git a/examples/lit/sorting/package.json b/examples/lit/sorting/package.json index 9958736516..74104aaa9e 100644 --- a/examples/lit/sorting/package.json +++ b/examples/lit/sorting/package.json @@ -1,21 +1,21 @@ { "name": "tanstack-lit-table-example-sorting", - "version": "0.0.0", "private": true, "scripts": { "dev": "vite", "build": "vite build", "serve": "vite preview", - "start": "vite" + "start": "vite", + "lint": "eslint ./src" }, "dependencies": { - "@faker-js/faker": "^8.4.1", - "@tanstack/lit-table": "^8.20.5", - "lit": "^3.1.4" + "@faker-js/faker": "^10.4.0", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "lit": "^3.3.2" }, "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/lit/sorting/src/main.ts b/examples/lit/sorting/src/main.ts index e111ac50c6..ebb396c7fb 100644 --- a/examples/lit/sorting/src/main.ts +++ b/examples/lit/sorting/src/main.ts @@ -1,54 +1,56 @@ -import { customElement } from 'lit/decorators.js' -import { html, LitElement } from 'lit' +import { customElement, state } from 'lit/decorators.js' +import { LitElement, html } from 'lit' import { repeat } from 'lit/directives/repeat.js' -import { state } from 'lit/decorators/state.js' import { - ColumnDef, - flexRender, - getCoreRowModel, - getSortedRowModel, - SortingFn, - type SortingState, + FlexRender, TableController, + createSortedRowModel, + rowSortingFeature, + sortFns, + tableFeatures, } from '@tanstack/lit-table' +import { Person, makeData } from './makeData' +import type { ColumnDef, SortFn } from '@tanstack/lit-table' -import { makeData, Person } from './makeData' +const _features = tableFeatures({ + rowSortingFeature, +}) -const sortStatusFn: SortingFn = (rowA, rowB, _columnId) => { +const sortStatusFn: SortFn = (rowA, rowB, _columnId) => { const statusA = rowA.original.status const statusB = rowB.original.status const statusOrder = ['single', 'complicated', 'relationship'] return statusOrder.indexOf(statusA) - statusOrder.indexOf(statusB) } -const columns: ColumnDef[] = [ +const columns: Array> = [ { accessorKey: 'firstName', - cell: info => info.getValue(), - //this column will sort in ascending order by default since it is a string column + cell: (info) => info.getValue(), + // this column will sort in ascending order by default since it is a string column }, { - accessorFn: row => row.lastName, + accessorFn: (row) => row.lastName, id: 'lastName', - cell: info => info.getValue(), + cell: (info) => info.getValue(), header: () => html`Last Name`, - sortUndefined: 'last', //force undefined values to the end - sortDescFirst: false, //first sort order will be ascending (nullable values can mess up auto detection of sort order) + sortUndefined: 'last', // force undefined values to the end + sortDescFirst: false, // first sort order will be ascending (nullable values can mess up auto detection of sort order) }, { accessorKey: 'age', header: () => 'Age', - //this column will sort in descending order by default since it is a number column + // this column will sort in descending order by default since it is a number column }, { accessorKey: 'visits', header: () => html`Visits`, - sortUndefined: 'last', //force undefined values to the end + sortUndefined: 'last', // force undefined values to the end }, { accessorKey: 'status', header: 'Status', - sortingFn: sortStatusFn, //use our custom sorting function for this enum column + sortFn: sortStatusFn, // use our custom sorting function for this enum column }, { accessorKey: 'progress', @@ -58,52 +60,61 @@ const columns: ColumnDef[] = [ { accessorKey: 'rank', header: 'Rank', - invertSorting: true, //invert the sorting order (golf score-like where smaller is better) + invertSorting: true, // invert the sorting order (golf score-like where smaller is better) }, { accessorKey: 'createdAt', header: 'Created At', - // sortingFn: 'datetime' //make sure table knows this is a datetime column (usually can detect if no null values) + // sortFn: 'datetime' //make sure table knows this is a datetime column (usually can detect if no null values) }, ] -const data: Person[] = makeData(1000) - @customElement('lit-table-example') class LitTableExample extends LitElement { @state() - private _sorting: SortingState = [] + private _data: Array = makeData(1_000) - private tableController = new TableController(this) + private tableController = new TableController(this) protected render() { - const table = this.tableController.table({ - columns, - data, - state: { - sorting: this._sorting, - }, - onSortingChange: updaterOrValue => { - if (typeof updaterOrValue === 'function') { - this._sorting = updaterOrValue(this._sorting) - } else { - this._sorting = updaterOrValue - } + const table = this.tableController.table( + { + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + }, + columns, + data: this._data, }, - getSortedRowModel: getSortedRowModel(), - getCoreRowModel: getCoreRowModel(), - }) + (state) => ({ sorting: state.sorting }), + ) return html` +
+ + +
${repeat( table.getHeaderGroups(), - headerGroup => headerGroup.id, - headerGroup => html` + (headerGroup) => headerGroup.id, + (headerGroup) => html` ${headerGroup.headers.map( - header => html` + (header) => html` - ` + `, )} - ` + `, )} @@ -140,26 +148,17 @@ class LitTableExample extends LitElement { .getRowModel() .rows.slice(0, 10) .map( - row => html` + (row) => html` ${row - .getVisibleCells() - .map( - cell => html` - - ` - )} + .getAllCells() + .map((cell) => html` `)} - ` + `, )}
${header.isPlaceholder ? null @@ -120,19 +131,16 @@ class LitTableExample extends LitElement { ? 'pointer' : 'not-allowed'}" > - ${flexRender( - header.column.columnDef.header, - header.getContext() - )} + ${FlexRender({ header })} ${{ asc: ' 🔼', desc: ' 🔽' }[ header.column.getIsSorted() as string ] ?? null} `}
- ${flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - ${FlexRender({ cell })}
-
${JSON.stringify(this._sorting, null, 2)}
+
${JSON.stringify(table.state.sorting, null, 2)}
` } diff --git a/examples/lit/sorting/src/makeData.ts b/examples/lit/sorting/src/makeData.ts index d6c0639b22..fc070cd5d2 100644 --- a/examples/lit/sorting/src/makeData.ts +++ b/examples/lit/sorting/src/makeData.ts @@ -9,11 +9,11 @@ export type Person = { status: 'relationship' | 'complicated' | 'single' rank: number createdAt: Date - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -32,14 +32,14 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], rank: faker.number.int(100), } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] return range(len).map((_d): Person => { return { ...newPerson(), diff --git a/examples/lit/sorting/tsconfig.json b/examples/lit/sorting/tsconfig.json index 56517d3a72..22d0d1157a 100644 --- a/examples/lit/sorting/tsconfig.json +++ b/examples/lit/sorting/tsconfig.json @@ -4,8 +4,6 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, @@ -14,12 +12,11 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "useDefineForClassFields": false, - - /* Linting */ "strict": true, "noUnusedLocals": false, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, - "include": ["src"] + "include": ["src", "vite.config.js", "vite.config.ts"] } diff --git a/examples/lit/sub-components/index.html b/examples/lit/sub-components/index.html new file mode 100644 index 0000000000..89415ec479 --- /dev/null +++ b/examples/lit/sub-components/index.html @@ -0,0 +1,13 @@ + + + + + + TanStack Lit Table - Sub Components + + +
+ + + + diff --git a/examples/lit/sub-components/package.json b/examples/lit/sub-components/package.json new file mode 100644 index 0000000000..c897790906 --- /dev/null +++ b/examples/lit/sub-components/package.json @@ -0,0 +1,21 @@ +{ + "name": "tanstack-lit-table-example-sub-components", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "lit": "^3.3.2" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/lit/sub-components/src/main.ts b/examples/lit/sub-components/src/main.ts new file mode 100644 index 0000000000..4918674afe --- /dev/null +++ b/examples/lit/sub-components/src/main.ts @@ -0,0 +1,400 @@ +import { customElement, state } from 'lit/decorators.js' +import { LitElement, html } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { + FlexRender, + TableController, + createExpandedRowModel, + rowExpandingFeature, + tableFeatures, +} from '@tanstack/lit-table' +import { makeData } from './makeData' +import type { ColumnDef, Row } from '@tanstack/lit-table' +import type { Person } from './makeData' + +const _features = tableFeatures({ rowExpandingFeature }) + +const columns: Array> = [ + { + id: 'expander', + header: () => null, + cell: ({ row }) => + row.getCanExpand() + ? html`` + : html`🔵`, + }, + { + accessorKey: 'firstName', + header: 'First Name', + cell: ({ row, getValue }) => html` +
${getValue()}
+ `, + footer: (props) => props.column.id, + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => info.getValue(), + header: () => html`Last Name`, + footer: (props) => props.column.id, + }, + { + accessorKey: 'age', + header: () => 'Age', + footer: (props) => props.column.id, + }, + { + accessorKey: 'visits', + header: () => html`Visits`, + footer: (props) => props.column.id, + }, + { + accessorKey: 'status', + header: 'Status', + footer: (props) => props.column.id, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + footer: (props) => props.column.id, + }, +] + +function renderSubComponent(row: Row) { + return html` +
+${JSON.stringify(row.original, null, 2)}
+ ` +} + +@customElement('lit-table-example') +class LitTableExample extends LitElement { + @state() + private _data: Array = makeData(10, 5) + + private tableController = new TableController(this) + + protected render() { + const table = this.tableController.table( + { + _features, + _rowModels: { + expandedRowModel: createExpandedRowModel(), + }, + columns, + data: this._data, + getRowCanExpand: () => true, + }, + (state) => ({ + expanded: state.expanded, + }), + ) + + return html` +
+
+ + +
+
+ + + ${repeat( + table.getHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` + + ${headerGroup.headers.map( + (header) => html` + + `, + )} + + `, + )} + + + ${table.getRowModel().rows.map( + (row) => html` + + ${row + .getAllCells() + .map((cell) => html` `)} + + ${row.getIsExpanded() + ? html` + + + + ` + : null} + `, + )} + +
+ ${header.isPlaceholder + ? null + : html`
${FlexRender({ header })}
`} +
${FlexRender({ cell })}
+ ${renderSubComponent(row)} +
+
+
${table.getRowModel().rows.length.toLocaleString()} Rows
+
+ + ` + } +} diff --git a/examples/lit/sub-components/src/makeData.ts b/examples/lit/sub-components/src/makeData.ts new file mode 100644 index 0000000000..d63c724b74 --- /dev/null +++ b/examples/lit/sub-components/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/lit/sub-components/tsconfig.json b/examples/lit/sub-components/tsconfig.json new file mode 100644 index 0000000000..02eb5e4c87 --- /dev/null +++ b/examples/lit/sub-components/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "emitDecoratorMetadata": true, + "noEmit": true, + "jsx": "react-jsx", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/lit/sub-components/vite.config.js b/examples/lit/sub-components/vite.config.js new file mode 100644 index 0000000000..fa3b238ac6 --- /dev/null +++ b/examples/lit/sub-components/vite.config.js @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + ], +}) diff --git a/examples/lit/virtualized-columns/index.html b/examples/lit/virtualized-columns/index.html new file mode 100644 index 0000000000..19b0aacfe2 --- /dev/null +++ b/examples/lit/virtualized-columns/index.html @@ -0,0 +1,13 @@ + + + + + + TanStack Lit Table - Virtualized Columns + + +
+ + + + diff --git a/examples/lit/virtualized-columns/package.json b/examples/lit/virtualized-columns/package.json new file mode 100644 index 0000000000..9fc2c4cbbc --- /dev/null +++ b/examples/lit/virtualized-columns/package.json @@ -0,0 +1,22 @@ +{ + "name": "tanstack-lit-table-example-virtualized-columns", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "@tanstack/lit-virtual": "^3.13.25", + "lit": "^3.3.2" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/lit/virtualized-columns/src/main.ts b/examples/lit/virtualized-columns/src/main.ts new file mode 100644 index 0000000000..af4a30a00c --- /dev/null +++ b/examples/lit/virtualized-columns/src/main.ts @@ -0,0 +1,516 @@ +import { customElement, state } from 'lit/decorators.js' +import { LitElement, html } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { + FlexRender, + TableController, + columnSizingFeature, + columnVisibilityFeature, + createSortedRowModel, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/lit-table' +import { styleMap } from 'lit/directives/style-map.js' +import { Ref, createRef, ref } from 'lit/directives/ref.js' +import { VirtualizerController } from '@tanstack/lit-virtual' +import { Person, makeColumns, makeData } from './makeData.ts' +import type { ColumnDef } from '@tanstack/lit-table' + +const _features = tableFeatures({ + columnSizingFeature, + columnVisibilityFeature, + rowSortingFeature, +}) + +const columns = makeColumns(1_000) as Array> +const data = makeData(1_000, columns) + +@customElement('lit-table-example') +class LitTableExample extends LitElement { + private tableController = new TableController(this) + + private tableContainerRef: Ref = createRef() + + @state() + private _data = data + + private columnVirtualizerController!: VirtualizerController + private rowVirtualizerController!: VirtualizerController + + connectedCallback() { + super.connectedCallback() + } + + private _ensureVirtualizers( + visibleColumnCount: number, + rowCount: number, + visibleColumns: Array, + ) { + // Create column virtualizer once we have the scroll element + if (!this.columnVirtualizerController) { + this.columnVirtualizerController = new VirtualizerController(this, { + count: visibleColumnCount, + estimateSize: (index: number) => + visibleColumns[index]?.getSize() ?? 150, + getScrollElement: () => this.tableContainerRef.value!, + horizontal: true, + overscan: 3, + }) + } + + if (!this.rowVirtualizerController) { + this.rowVirtualizerController = new VirtualizerController(this, { + count: rowCount, + estimateSize: () => 33, + getScrollElement: () => this.tableContainerRef.value!, + overscan: 5, + }) + } + } + + private _refreshData() { + this._data = makeData(1_000, columns) + } + + protected render() { + const table = this.tableController.table( + { + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + }, + columns, + data: this._data, + }, + () => ({}), + ) + + const visibleColumns = table.getVisibleLeafColumns() + const { rows } = table.getRowModel() + + this._ensureVirtualizers(visibleColumns.length, rows.length, visibleColumns) + + const columnVirtualizer = this.columnVirtualizerController?.getVirtualizer() + const rowVirtualizer = this.rowVirtualizerController?.getVirtualizer() + + if (!columnVirtualizer || !rowVirtualizer) { + return html`
Loading...
` + } + + // Update virtualizer counts + columnVirtualizer.setOptions({ + ...columnVirtualizer.options, + count: visibleColumns.length, + estimateSize: (index: number) => visibleColumns[index]?.getSize() ?? 150, + }) + rowVirtualizer.setOptions({ + ...rowVirtualizer.options, + count: rows.length, + }) + + const virtualColumns = columnVirtualizer.getVirtualItems() + const virtualRows = rowVirtualizer.getVirtualItems() + + let virtualPaddingLeft: number | undefined + let virtualPaddingRight: number | undefined + + if (virtualColumns.length > 0) { + virtualPaddingLeft = virtualColumns[0]?.start ?? 0 + virtualPaddingRight = + columnVirtualizer.getTotalSize() - + (virtualColumns[virtualColumns.length - 1]?.end ?? 0) + } + + return html` +
+
(${columns.length.toLocaleString()} columns)
+
(${this._data.length.toLocaleString()} rows)
+ + +
+ + + ${repeat( + table.getHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` + + ${virtualPaddingLeft + ? html`` + : null} + ${repeat( + virtualColumns, + (vc) => vc.index, + (virtualColumn) => { + const header = headerGroup.headers[virtualColumn.index] + return html` + + ` + }, + )} + ${virtualPaddingRight + ? html`` + : null} + + `, + )} + + + ${repeat( + virtualRows, + (item) => item.key, + (virtualRow) => { + const row = rows[virtualRow.index] + const visibleCells = row.getVisibleCells() + return html` + + rowVirtualizer.measureElement(node ?? null), + )} + > + ${virtualPaddingLeft + ? html`` + : null} + ${repeat( + virtualColumns, + (vc) => vc.index, + (virtualColumn) => { + const cell = visibleCells[virtualColumn.index] + return html` + + ` + }, + )} + ${virtualPaddingRight + ? html`` + : null} + + ` + }, + )} + +
+ ${FlexRender({ header })} + ${{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null} +
+ ${FlexRender({ cell })} +
+
+
+ + + ` + } +} diff --git a/examples/lit/virtualized-columns/src/makeData.ts b/examples/lit/virtualized-columns/src/makeData.ts new file mode 100644 index 0000000000..837114e211 --- /dev/null +++ b/examples/lit/virtualized-columns/src/makeData.ts @@ -0,0 +1,17 @@ +import { faker } from '@faker-js/faker' + +export const makeColumns = (num: number) => + [...Array(num)].map((_, i) => ({ + accessorKey: i.toString(), + header: 'Column ' + i.toString(), + size: Math.floor(Math.random() * 150) + 100, + })) + +export const makeData = (num: number, columns: Array) => + [...Array(num)].map(() => ({ + ...Object.fromEntries( + columns.map((col: any) => [col.accessorKey, faker.person.firstName()]), + ), + })) + +export type Person = ReturnType[0] diff --git a/examples/lit/virtualized-columns/tsconfig.json b/examples/lit/virtualized-columns/tsconfig.json new file mode 100644 index 0000000000..02eb5e4c87 --- /dev/null +++ b/examples/lit/virtualized-columns/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "emitDecoratorMetadata": true, + "noEmit": true, + "jsx": "react-jsx", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/lit/virtualized-columns/vite.config.js b/examples/lit/virtualized-columns/vite.config.js new file mode 100644 index 0000000000..0e960eede3 --- /dev/null +++ b/examples/lit/virtualized-columns/vite.config.js @@ -0,0 +1,13 @@ +import { defineConfig } from 'vite' +import rollupReplace from '@rollup/plugin-replace' +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + ], +}) diff --git a/examples/lit/virtualized-infinite-scrolling/index.html b/examples/lit/virtualized-infinite-scrolling/index.html new file mode 100644 index 0000000000..29b495b0bb --- /dev/null +++ b/examples/lit/virtualized-infinite-scrolling/index.html @@ -0,0 +1,13 @@ + + + + + + TanStack Lit Table - Virtualized Infinite Scrolling + + +
+ + + + diff --git a/examples/lit/virtualized-infinite-scrolling/package.json b/examples/lit/virtualized-infinite-scrolling/package.json new file mode 100644 index 0000000000..f07e9178c2 --- /dev/null +++ b/examples/lit/virtualized-infinite-scrolling/package.json @@ -0,0 +1,22 @@ +{ + "name": "tanstack-lit-table-example-virtualized-infinite-scrolling", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "@tanstack/lit-virtual": "^3.13.25", + "lit": "^3.3.2" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/lit/virtualized-infinite-scrolling/src/main.ts b/examples/lit/virtualized-infinite-scrolling/src/main.ts new file mode 100644 index 0000000000..462e64799e --- /dev/null +++ b/examples/lit/virtualized-infinite-scrolling/src/main.ts @@ -0,0 +1,533 @@ +import { customElement, state } from 'lit/decorators.js' +import { LitElement, html } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { + FlexRender, + TableController, + columnSizingFeature, + createSortedRowModel, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/lit-table' +import { styleMap } from 'lit/directives/style-map.js' +import { Ref, createRef, ref } from 'lit/directives/ref.js' +import { VirtualizerController } from '@tanstack/lit-virtual' +import { fetchData } from './makeData.ts' +import type { Person } from './makeData.ts' +import type { ColumnDef, SortingState } from '@tanstack/lit-table' + +const fetchSize = 50 + +const _features = tableFeatures({ + columnSizingFeature, + rowSortingFeature, +}) + +const columns: Array> = [ + { + accessorKey: 'id', + header: 'ID', + size: 60, + }, + { + accessorKey: 'firstName', + cell: (info) => info.getValue(), + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => info.getValue(), + header: () => html`Last Name`, + }, + { + accessorKey: 'age', + header: () => 'Age', + size: 50, + }, + { + accessorKey: 'visits', + header: () => html`Visits`, + size: 50, + }, + { + accessorKey: 'status', + header: 'Status', + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + size: 80, + }, + { + accessorKey: 'createdAt', + header: 'Created At', + cell: (info) => info.getValue().toLocaleString(), + size: 200, + }, +] + +@customElement('lit-table-example') +class LitTableExample extends LitElement { + private tableController = new TableController(this) + + private tableContainerRef: Ref = createRef() + + private rowVirtualizerController!: VirtualizerController + + @state() + private _data: Array = [] + + @state() + private _isFetching = false + + @state() + private _totalRowCount = 0 + + @state() + private _sorting: SortingState = [] + + connectedCallback() { + super.connectedCallback() + // Fetch initial data + this._fetchMoreData() + } + + private async _fetchMoreData() { + if (this._isFetching) return + + this._isFetching = true + + try { + const response = await fetchData( + this._data.length, + fetchSize, + this._sorting, + ) + this._data = [...this._data, ...response.data] + this._totalRowCount = response.meta.totalRowCount + } finally { + this._isFetching = false + } + } + + private async _refetchData() { + this._isFetching = true + this._data = [] + + try { + const response = await fetchData(0, fetchSize, this._sorting) + this._data = response.data + this._totalRowCount = response.meta.totalRowCount + } finally { + this._isFetching = false + } + } + + private _handleScroll(e: Event) { + const target = e.currentTarget as HTMLDivElement + const { scrollHeight, scrollTop, clientHeight } = target + if ( + scrollHeight - scrollTop - clientHeight < 500 && + !this._isFetching && + this._data.length < this._totalRowCount + ) { + this._fetchMoreData() + } + } + + protected render() { + const table = this.tableController.table( + { + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + }, + columns, + data: this._data, + state: { + sorting: this._sorting, + }, + manualSorting: true, + onSortingChange: (updater) => { + const newSorting = + typeof updater === 'function' ? updater(this._sorting) : updater + this._sorting = newSorting + // Reset data and refetch when sorting changes + this._refetchData() + // Scroll back to top + if (this.tableContainerRef.value) { + this.tableContainerRef.value.scrollTop = 0 + } + }, + }, + () => ({}), + ) + + const { rows } = table.getRowModel() + + if (!this.rowVirtualizerController) { + this.rowVirtualizerController = new VirtualizerController(this, { + count: rows.length, + estimateSize: () => 33, + getScrollElement: () => this.tableContainerRef.value!, + overscan: 5, + }) + } + + const virtualizer = this.rowVirtualizerController.getVirtualizer() + + // Update count when rows change + virtualizer.setOptions({ + ...virtualizer.options, + count: rows.length, + }) + + const virtualRows = virtualizer.getVirtualItems() + + return html` +
+ (${this._data.length.toLocaleString()} of + ${this._totalRowCount.toLocaleString()} rows fetched) +
+ + + ${repeat( + table.getHeaderGroups(), + (headerGroup) => headerGroup.id, + (headerGroup) => html` + + ${repeat( + headerGroup.headers, + (header) => header.id, + (header) => html` + + `, + )} + + `, + )} + + + ${repeat( + virtualRows, + (item) => item.key, + (virtualRow) => { + const row = rows[virtualRow.index] + return html` + virtualizer.measureElement(node ?? null))} + > + ${repeat( + row.getAllCells(), + (cell) => cell.id, + (cell) => html` + + `, + )} + + ` + }, + )} + +
+ ${FlexRender({ header })} + ${{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null} +
+ ${FlexRender({ cell })} +
+
+ ${this._isFetching ? html`
Fetching More...
` : null} +
+ + + ` + } +} diff --git a/examples/lit/virtualized-infinite-scrolling/src/makeData.ts b/examples/lit/virtualized-infinite-scrolling/src/makeData.ts new file mode 100644 index 0000000000..956ccda363 --- /dev/null +++ b/examples/lit/virtualized-infinite-scrolling/src/makeData.ts @@ -0,0 +1,89 @@ +import { faker } from '@faker-js/faker' +import type { SortingState } from '@tanstack/lit-table' + +export type Person = { + id: number + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + createdAt: Date +} + +export type PersonApiResponse = { + data: Array + meta: { + totalRowCount: number + } +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (index: number): Person => { + return { + id: index + 1, + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + createdAt: faker.date.anytime(), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((d): Person => { + return { + ...newPerson(d), + } + }) + } + + return makeDataLevel() +} + +const data = makeData(1000) + +// simulates a backend api +export const fetchData = async ( + start: number, + size: number, + sorting: SortingState, +): Promise => { + const dbData = [...data] + if (sorting.length) { + const sort = sorting[0] + const { id, desc } = sort as { id: keyof Person; desc: boolean } + dbData.sort((a, b) => { + if (desc) { + return a[id] < b[id] ? 1 : -1 + } + return a[id] > b[id] ? 1 : -1 + }) + } + + // simulate a backend api + await new Promise((resolve) => setTimeout(resolve, 200)) + + return { + data: dbData.slice(start, start + size), + meta: { + totalRowCount: dbData.length, + }, + } +} diff --git a/examples/lit/virtualized-infinite-scrolling/tsconfig.json b/examples/lit/virtualized-infinite-scrolling/tsconfig.json new file mode 100644 index 0000000000..02eb5e4c87 --- /dev/null +++ b/examples/lit/virtualized-infinite-scrolling/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "emitDecoratorMetadata": true, + "noEmit": true, + "jsx": "react-jsx", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/lit/virtualized-infinite-scrolling/vite.config.js b/examples/lit/virtualized-infinite-scrolling/vite.config.js new file mode 100644 index 0000000000..0e960eede3 --- /dev/null +++ b/examples/lit/virtualized-infinite-scrolling/vite.config.js @@ -0,0 +1,13 @@ +import { defineConfig } from 'vite' +import rollupReplace from '@rollup/plugin-replace' +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + ], +}) diff --git a/examples/lit/virtualized-rows/index.html b/examples/lit/virtualized-rows/index.html index 9807874fae..15b0437b92 100644 --- a/examples/lit/virtualized-rows/index.html +++ b/examples/lit/virtualized-rows/index.html @@ -4,7 +4,6 @@ Vite App -
diff --git a/examples/lit/virtualized-rows/package.json b/examples/lit/virtualized-rows/package.json index 077611a9c0..4761c1202c 100644 --- a/examples/lit/virtualized-rows/package.json +++ b/examples/lit/virtualized-rows/package.json @@ -1,22 +1,22 @@ { - "name": "tanstack-lit-table-virtualized-rows", - "version": "0.0.0", + "name": "tanstack-lit-table-example-virtualized-rows", "private": true, "scripts": { "dev": "vite", "build": "vite build", "serve": "vite preview", - "start": "vite" + "start": "vite", + "lint": "eslint ./src" }, "dependencies": { - "@faker-js/faker": "^8.4.1", - "@tanstack/lit-table": "^8.20.5", - "@tanstack/lit-virtual": "^3.8.3", - "lit": "^3.1.4" + "@faker-js/faker": "^10.4.0", + "@tanstack/lit-table": "^9.0.0-alpha.45", + "@tanstack/lit-virtual": "^3.13.25", + "lit": "^3.3.2" }, "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@rollup/plugin-replace": "^6.0.3", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/lit/virtualized-rows/src/main.ts b/examples/lit/virtualized-rows/src/main.ts index d11e3fd0dd..0d57792563 100644 --- a/examples/lit/virtualized-rows/src/main.ts +++ b/examples/lit/virtualized-rows/src/main.ts @@ -1,21 +1,27 @@ -import { customElement } from 'lit/decorators.js' -import { html, LitElement } from 'lit' +import { customElement, state } from 'lit/decorators.js' +import { LitElement, html } from 'lit' import { repeat } from 'lit/directives/repeat.js' import { - ColumnDef, - flexRender, - getCoreRowModel, - getSortedRowModel, - Row, + FlexRender, TableController, + columnSizingFeature, + createSortedRowModel, + rowSortingFeature, + sortFns, + tableFeatures, } from '@tanstack/lit-table' import { styleMap } from 'lit/directives/style-map.js' - -import { makeData, Person } from './makeData.ts' +import { Ref, createRef, ref } from 'lit/directives/ref.js' import { VirtualizerController } from '@tanstack/lit-virtual' -import { createRef, ref, Ref } from 'lit/directives/ref.js' +import { Person, makeData } from './makeData.ts' +import type { ColumnDef } from '@tanstack/lit-table' + +const _features = tableFeatures({ + columnSizingFeature, + rowSortingFeature, +}) -const columns: ColumnDef[] = [ +const columns: Array> = [ { accessorKey: 'id', header: 'ID', @@ -23,12 +29,12 @@ const columns: ColumnDef[] = [ }, { accessorKey: 'firstName', - cell: info => info.getValue(), + cell: (info) => info.getValue(), }, { - accessorFn: row => row.lastName, + accessorFn: (row) => row.lastName, id: 'lastName', - cell: info => info.getValue(), + cell: (info) => info.getValue(), header: () => html`Last Name`, }, { @@ -53,15 +59,16 @@ const columns: ColumnDef[] = [ { accessorKey: 'createdAt', header: 'Created At', - cell: info => info.getValue().toLocaleString(), + cell: (info) => info.getValue().toLocaleString(), size: 250, }, ] -const data = makeData(50_000) - @customElement('lit-table-example') class LitTableExample extends LitElement { - private tableController = new TableController(this) + @state() + private _data: Array = makeData(50_000) + + private tableController = new TableController(this) private tableContainerRef: Ref = createRef() @@ -69,7 +76,7 @@ class LitTableExample extends LitElement { connectedCallback() { this.rowVirtualizerController = new VirtualizerController(this, { - count: data.length, + count: this._data.length, getScrollElement: () => this.tableContainerRef.value!, estimateSize: () => 33, overscan: 5, @@ -77,26 +84,51 @@ class LitTableExample extends LitElement { super.connectedCallback() } - protected render(): unknown { - const table = this.tableController.table({ - columns, - data, - getSortedRowModel: getSortedRowModel(), - getCoreRowModel: getCoreRowModel(), - }) + protected render() { + const table = this.tableController.table( + { + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + }, + columns, + data: this._data, + }, + () => ({}), // selector - empty since we don't need any state + ) const { rows } = table.getRowModel() const virtualizer = this.rowVirtualizerController.getVirtualizer() + virtualizer.setOptions({ + ...virtualizer.options, + count: rows.length, + }) return html`
- (${data.length} rows) +
+ + +
+ (${this._data.length.toLocaleString()} rows)
@@ -110,13 +142,13 @@ class LitTableExample extends LitElement { > ${repeat( table.getHeaderGroups(), - headerGroup => headerGroup.id, - headerGroup => html` + (headerGroup) => headerGroup.id, + (headerGroup) => html` ${repeat( headerGroup.headers, - header => header.id, - header => html` + (header) => header.id, + (header) => html` - ` + `, )} - ` + `, )} ${repeat( this.rowVirtualizerController .getVirtualizer() .getVirtualItems(), - item => item.key, - item => { - const row = rows[item.index] as Row + (item) => item.key, + (item) => { + const row = rows[item.index] return html` + ${ref((node) => this.rowVirtualizerController .getVirtualizer() - .measureElement(node) + .measureElement(node ?? null), )} > ${repeat( - row.getVisibleCells(), - cell => cell.id, - cell => html` + row.getAllCells(), + (cell) => cell.id, + (cell) => html` - ` + `, )} ` - } + }, )}
- ${flexRender( - header.column.columnDef.header, - header.getContext() - )} + ${FlexRender({ header })} ${{ asc: ' 🔼', desc: ' 🔽' }[ header.column.getIsSorted() as string ] ?? null}
- ${flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} + ${FlexRender({ cell })}
@@ -199,8 +225,6 @@ class LitTableExample extends LitElement { } table { - border-collapse: collapse; - border-spacing: 0; font-family: arial, sans-serif; table-layout: fixed; } @@ -233,6 +257,210 @@ class LitTableExample extends LitElement { margin: 1rem auto; text-align: center; } + + /* Demo layout helpers for the plain example UI. */ + .demo-root { + padding: 0.5rem; + } + .spacer-xs { + height: 0.25rem; + } + .spacer-sm { + height: 0.5rem; + } + .spacer-md { + height: 1rem; + } + .controls, + .button-row, + .inline-controls, + .pin-actions, + .filter-row, + .form-actions { + display: flex; + align-items: center; + } + .button-row { + flex-wrap: wrap; + gap: 0.5rem; + } + .controls { + gap: 0.5rem; + } + .inline-controls, + .pin-actions { + gap: 0.25rem; + } + .pin-actions { + justify-content: center; + } + .filter-row { + gap: 0.5rem; + } + .form-actions { + gap: 1rem; + margin-bottom: 1rem; + } + .split-tables { + display: flex; + gap: 1rem; + } + .table-row-group { + display: flex; + } + .split-gap { + gap: 1rem; + } + .vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; + } + .column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + } + .column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; + } + .column-toggle-row, + .selection-cell { + padding: 0 0.25rem; + } + .selection-cell { + display: block; + } + .demo-button, + .pin-button, + .compact-input, + .filter-input, + .filter-select, + .page-size-input, + .text-input, + .number-input, + .wide-action-button, + .primary-action, + .secondary-action, + .success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; + } + .demo-button { + padding: 0.5rem; + } + .demo-button-sm { + padding: 0.25rem; + } + .demo-button-spaced { + margin-bottom: 0.5rem; + } + .pin-button { + padding: 0 0.5rem; + } + .outlined-table { + border: 2px solid #000; + } + .outlined-control { + border-color: #000; + } + .nowrap { + white-space: nowrap; + } + .demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; + } + .section-title { + font-size: 1.25rem; + } + .scroll-container { + overflow-x: auto; + } + .page-size-input { + width: 4rem; + padding: 0.25rem; + } + .number-input { + width: 5rem; + padding: 0 0.25rem; + } + .filter-input, + .filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + } + .filter-select { + width: 9rem; + } + .text-input { + width: 100%; + padding: 0 0.25rem; + } + .compact-input { + padding: 0 0.25rem; + } + .wide-action-button { + width: 16rem; + } + .summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; + } + .sortable-header, + .sortable { + cursor: pointer; + user-select: none; + } + .primary-action, + .success-action, + .secondary-action { + color: #fff; + } + .primary-action { + background: #3b82f6; + } + .success-action { + background: #22c55e; + } + .secondary-action { + background: #6b7280; + } + .submit-button:disabled { + opacity: 0.5; + } + .error-text { + color: #ef4444; + font-size: 0.75rem; + } + .success-text { + color: #16a34a; + } + .warning-text { + color: #ca8a04; + } + .muted-text { + color: #9ca3af; + } + .label-offset { + margin-left: 0.5rem; + } + .cell-padding { + padding: 0.25rem; + } + .table-spacer { + margin-bottom: 0.5rem; + } + .centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; + } ` } diff --git a/examples/lit/virtualized-rows/src/makeData.ts b/examples/lit/virtualized-rows/src/makeData.ts index e5695467f5..5fd0293fef 100644 --- a/examples/lit/virtualized-rows/src/makeData.ts +++ b/examples/lit/virtualized-rows/src/makeData.ts @@ -12,7 +12,7 @@ export type Person = { } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -32,13 +32,13 @@ const newPerson = (index: number): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] return range(len).map((d): Person => { return { ...newPerson(d), diff --git a/examples/lit/virtualized-rows/tsconfig.json b/examples/lit/virtualized-rows/tsconfig.json index f90811f312..2bf3a5d18d 100644 --- a/examples/lit/virtualized-rows/tsconfig.json +++ b/examples/lit/virtualized-rows/tsconfig.json @@ -4,8 +4,6 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, @@ -16,12 +14,11 @@ "experimentalDecorators": true, "useDefineForClassFields": false, "strictPropertyInitialization": false, - - /* Linting */ "strict": true, "noUnusedLocals": false, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, - "include": ["src"] + "include": ["src", "vite.config.js", "vite.config.ts"] } diff --git a/examples/lit/virtualized-rows/twind.config.ts b/examples/lit/virtualized-rows/twind.config.ts deleted file mode 100644 index f7e85ff4ae..0000000000 --- a/examples/lit/virtualized-rows/twind.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from '@twind/core' -import presetAutoprefix from '@twind/preset-autoprefix' -import presetTailwind from '@twind/preset-tailwind/base' - -export default defineConfig({ - presets: [presetAutoprefix(), presetTailwind()], -}) diff --git a/examples/preact/basic-external-atoms/index.html b/examples/preact/basic-external-atoms/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/basic-external-atoms/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/basic-external-atoms/package.json b/examples/preact/basic-external-atoms/package.json new file mode 100644 index 0000000000..3cf7100a48 --- /dev/null +++ b/examples/preact/basic-external-atoms/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-preact-table-example-basic-external-atoms", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/preact-store": "^0.13.1", + "@tanstack/preact-table": "^9.0.0-alpha.45", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/preact/basic-external-atoms/src/index.css b/examples/preact/basic-external-atoms/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/preact/basic-external-atoms/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/preact/basic-external-atoms/src/main.tsx b/examples/preact/basic-external-atoms/src/main.tsx new file mode 100644 index 0000000000..5ed17a896a --- /dev/null +++ b/examples/preact/basic-external-atoms/src/main.tsx @@ -0,0 +1,216 @@ +import { render } from 'preact' +import { useReducer, useState } from 'preact/hooks' +import './index.css' +import { useCreateAtom, useSelector } from '@tanstack/preact-store' +import { + createColumnHelper, + createPaginatedRowModel, + createSortedRowModel, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, + useTable, +} from '@tanstack/preact-table' +import { makeData } from './makeData' +import type { PaginationState, SortingState } from '@tanstack/preact-table' + +type Person = { + firstName: string + lastName: string + age: number + visits: number + status: string + progress: number +} + +const _features = tableFeatures({ + rowPaginationFeature, + rowSortingFeature, +}) + +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('lastName', { + header: 'Last Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('age', { + header: 'Age', + }), + columnHelper.accessor('visits', { + header: 'Visits', + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + }), +]) + +function App() { + const [data, setData] = useState(() => makeData(1_000)) + const refreshData = () => setData(makeData(1_000)) + const stressTest = () => setData(makeData(200_000)) + + const rerender = useReducer(() => ({}), {})[1] + + // Create stable external atoms for the individual state slices you want to + // own. The table still creates internal base atoms for everything else. + const sortingAtom = useCreateAtom([]) + const paginationAtom = useCreateAtom({ + pageIndex: 0, + pageSize: 10, + }) + + // Subscribe to each atom independently — fine-grained reactivity. + const sorting = useSelector(sortingAtom, (s) => s) + const pagination = useSelector(paginationAtom, (s) => s) + + // Create the table and pass your per-slice external atoms. + const table = useTable( + { + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data, + atoms: { + sorting: sortingAtom, + pagination: paginationAtom, + }, + debugTable: pagination.pageIndex > 2, + }, + (state) => state, // default selector + ) + + return ( +
+
+ + +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getAllCells().map((cell) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( +
+ + {{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null} +
+ )} +
+ +
+
+
+ + + + + +
Page
+ + {(pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = (e.target as HTMLInputElement).value + ? Number((e.target as HTMLInputElement).value) - 1 + : 0 + table.setPageIndex(page) + }} + className="page-size-input" + /> + + +
+
+ +
{JSON.stringify({ sorting, pagination }, null, 2)}
+
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render(, rootElement) diff --git a/examples/preact/basic-external-atoms/src/makeData.ts b/examples/preact/basic-external-atoms/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/preact/basic-external-atoms/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/qwik/basic/src/vite-env.d.ts b/examples/preact/basic-external-atoms/src/vite-env.d.ts similarity index 100% rename from examples/qwik/basic/src/vite-env.d.ts rename to examples/preact/basic-external-atoms/src/vite-env.d.ts diff --git a/examples/preact/basic-external-atoms/tsconfig.json b/examples/preact/basic-external-atoms/tsconfig.json new file mode 100644 index 0000000000..c1e0bb7b6c --- /dev/null +++ b/examples/preact/basic-external-atoms/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/basic-external-atoms/vite.config.ts b/examples/preact/basic-external-atoms/vite.config.ts new file mode 100644 index 0000000000..29b326faf0 --- /dev/null +++ b/examples/preact/basic-external-atoms/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact()], +}) diff --git a/examples/preact/basic-external-state/index.html b/examples/preact/basic-external-state/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/basic-external-state/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/basic-external-state/package.json b/examples/preact/basic-external-state/package.json new file mode 100644 index 0000000000..448fcdc378 --- /dev/null +++ b/examples/preact/basic-external-state/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-preact-table-example-basic-external-state", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/preact-store": "^0.13.1", + "@tanstack/preact-table": "^9.0.0-alpha.45", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/preact/basic-external-state/src/index.css b/examples/preact/basic-external-state/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/preact/basic-external-state/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/preact/basic-external-state/src/main.tsx b/examples/preact/basic-external-state/src/main.tsx new file mode 100644 index 0000000000..9057e5a3d5 --- /dev/null +++ b/examples/preact/basic-external-state/src/main.tsx @@ -0,0 +1,217 @@ +import { render } from 'preact' +import { useReducer, useState } from 'preact/hooks' +import './index.css' +import { + createColumnHelper, + createPaginatedRowModel, + createSortedRowModel, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, + useTable, +} from '@tanstack/preact-table' +import { makeData } from './makeData' +import type { PaginationState, SortingState } from '@tanstack/preact-table' + +type Person = { + firstName: string + lastName: string + age: number + visits: number + status: string + progress: number +} + +const _features = tableFeatures({ + rowPaginationFeature, + rowSortingFeature, +}) + +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('lastName', { + header: 'Last Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('age', { + header: 'Age', + }), + columnHelper.accessor('visits', { + header: 'Visits', + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + }), +]) + +function App() { + const [data, setData] = useState(() => makeData(1_000)) + const refreshData = () => setData(makeData(1_000)) + const stressTest = () => setData(makeData(200_000)) + + const rerender = useReducer(() => ({}), {})[1] + + // Manage sorting state with useState (although state causes more re-renders here than necessary compared to using a store) + const [sorting, setSorting] = useState([]) + + // Manage pagination state with useState (although state causes more re-renders here than necessary compared to using a store) + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 10, + }) + + console.log('sorting', sorting) + console.log('pagination', pagination) + + // Create the table and pass state + onChange handlers + const table = useTable( + { + debugTable: true, + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data, + state: { + sorting, // connect our sorting state back down to the table + pagination, // connect our pagination state back down to the table + }, + onSortingChange: setSorting, // raise sorting state changes to our own state management + onPaginationChange: setPagination, // raise pagination state changes to our own state management + }, + (state) => state, // default selector + ) + + return ( +
+
+ + +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getAllCells().map((cell) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( +
+ + {{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null} +
+ )} +
+ +
+
+
+ + + + + +
Page
+ + {(pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = (e.target as HTMLInputElement).value + ? Number((e.target as HTMLInputElement).value) - 1 + : 0 + table.setPageIndex(page) + }} + className="page-size-input" + /> + + +
+
+ +
{JSON.stringify({ sorting, pagination }, null, 2)}
+
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render(, rootElement) diff --git a/examples/preact/basic-external-state/src/makeData.ts b/examples/preact/basic-external-state/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/preact/basic-external-state/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/qwik/filters/src/vite-env.d.ts b/examples/preact/basic-external-state/src/vite-env.d.ts similarity index 100% rename from examples/qwik/filters/src/vite-env.d.ts rename to examples/preact/basic-external-state/src/vite-env.d.ts diff --git a/examples/preact/basic-external-state/tsconfig.json b/examples/preact/basic-external-state/tsconfig.json new file mode 100644 index 0000000000..c1e0bb7b6c --- /dev/null +++ b/examples/preact/basic-external-state/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/basic-external-state/vite.config.ts b/examples/preact/basic-external-state/vite.config.ts new file mode 100644 index 0000000000..29b326faf0 --- /dev/null +++ b/examples/preact/basic-external-state/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact()], +}) diff --git a/examples/preact/basic-subscribe/index.html b/examples/preact/basic-subscribe/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/basic-subscribe/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/basic-subscribe/package.json b/examples/preact/basic-subscribe/package.json new file mode 100644 index 0000000000..a388c719aa --- /dev/null +++ b/examples/preact/basic-subscribe/package.json @@ -0,0 +1,26 @@ +{ + "name": "tanstack-preact-table-example-basic-subscribe", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/preact-store": "^0.13.1", + "@tanstack/preact-table": "^9.0.0-alpha.45", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "@tanstack/preact-devtools": "^0.10.2", + "@tanstack/preact-table-devtools": "^9.0.0-alpha.43", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/preact/basic-subscribe/src/index.css b/examples/preact/basic-subscribe/src/index.css new file mode 100644 index 0000000000..0a099f6d9f --- /dev/null +++ b/examples/preact/basic-subscribe/src/index.css @@ -0,0 +1,372 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +button:disabled, +button[disabled] { + opacity: 0.5; + cursor: not-allowed; + pointer-events: none; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/preact/basic-subscribe/src/main.tsx b/examples/preact/basic-subscribe/src/main.tsx new file mode 100644 index 0000000000..167ad3b406 --- /dev/null +++ b/examples/preact/basic-subscribe/src/main.tsx @@ -0,0 +1,478 @@ +import { useEffect, useMemo, useReducer, useRef, useState } from 'preact/hooks' +import { render } from 'preact' +import { + Subscribe, + columnFilteringFeature, + createColumnHelper, + createFilteredRowModel, + createPaginatedRowModel, + filterFns, + globalFilteringFeature, + rowPaginationFeature, + rowSelectionFeature, + tableFeatures, + useTable, +} from '@tanstack/preact-table' +import { + tableDevtoolsPlugin, + useTanStackTableDevtools, +} from '@tanstack/preact-table-devtools' +import { TanStackDevtools } from '@tanstack/preact-devtools' +import { useCreateAtom } from '@tanstack/preact-store' +import { makeData } from './makeData' +import type { + Column, + PreactTable, + RowSelectionState, +} from '@tanstack/preact-table' +import type { JSX } from 'preact' +import type { Person } from './makeData' +import './index.css' + +const _features = tableFeatures({ + rowPaginationFeature, + rowSelectionFeature, + columnFilteringFeature, + globalFilteringFeature, +}) + +const columnHelper = createColumnHelper() + +/** + * This is an example showing how to use advanced re-rendering optimizations with more fine-grained control over what is subscribed to. + * Subscribe/table.Subscribe is a higher-order component that allows you to subscribe to the table state or individual atoms/stores. + * This is useful for making sure that re-renders only happen at certain parts of the preact tree exactly where need to be. + * We recommend only using these patterns when you run into specific performance issues. + */ +function App() { + const rerender = useReducer(() => ({}), {})[1] + + const columns = useMemo( + () => + columnHelper.columns([ + columnHelper.display({ + id: 'select', + header: ({ table }) => { + return ( + // just import Subscribe component if "preact" table is not available in scope and pass in the table store as source + ({ + columnFilters: state.columnFilters, + globalFilter: state.globalFilter, + rowSelection: state.rowSelection, + })} + > + {() => ( + + )} + + ) + }, + cell: ({ row }) => ( + rowSelection[row.id]} // optimize to only re-render when the row selection changes for this row + > + {(isRowSelected) => ( +
+ {/* Select only this row's selection value so toggling one row only re-renders that row's checkbox. */} + +
+ )} +
+ ), + }), + columnHelper.accessor('firstName', { + header: 'First Name', + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + header: () => Last Name, + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + }), + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), + ]), + [], + ) + + const [data, setData] = useState(() => makeData(1_000)) + const refreshData = () => setData(() => makeData(1_000)) + const stressTest = () => setData(() => makeData(200_000)) + + // optionally, raise the selection state to your own atom + const rowSelectionAtom = useCreateAtom({}) + + const table = useTable( + { + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + }, + atoms: { + rowSelection: rowSelectionAtom, + }, + columns, + data, + getRowId: (row) => row.id, + enableRowSelection: true, // enable row selection for all rows + // enableRowSelection: row => row.original.age > 18, // or enable row selection conditionally per row + debugTable: true, + }, + () => null, // subscribe to no table state by default; use table.Subscribe below for targeted updates + ) + + useTanStackTableDevtools(table, 'Basic Subscribe Example') + + return ( +
+
+ + +
+
+ + {(globalFilter) => ( + + table.setGlobalFilter((e.target as HTMLInputElement).value) + } + className="summary-panel" + placeholder="Search all columns..." + /> + )} + +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ) + })} + + ))} + + {/* Subscribe the row model to filtering and pagination only. Row selection is handled per row below. */} + ({ + columnFilters: state.columnFilters, + globalFilter: state.globalFilter, + pagination: state.pagination, + })} + > + {() => ( + <> + + {table.getRowModel().rows.map((row) => { + return ( + + {row.getAllCells().map((cell) => { + return ( + + ) + })} + + ) + })} + + + + + + + + + )} + +
+ {header.isPlaceholder ? null : ( + <> + + {header.column.getCanFilter() ? ( +
+ +
+ ) : null} + + )} +
+ +
+ + {() => ( + + )} + + + Page Rows ( + {table.getRowModel().rows.length.toLocaleString()}) +
+
+ ({ + columnFilters: state.columnFilters, + globalFilter: state.globalFilter, + pagination: state.pagination, + })} + > + {({ pagination }) => ( +
+ + + + + +
Page
+ + {(pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const value = (e.target as HTMLInputElement).value + const page = value ? Number(value) - 1 : 0 + table.setPageIndex(page) + }} + className="page-size-input" + /> + + +
+ )} +
+
+ + {(rowSelection) => ( +
+ <>{Object.keys(rowSelection).length.toLocaleString()} of + {table.getPreFilteredRowModel().rows.length.toLocaleString()} Total + Rows Selected +
+ )} +
+
+
+
+ +
+
+ +
+
+ + {/* subscribe to the entire table state */} + state}> + {(state) =>
{JSON.stringify(state, null, 2)}
} +
+
+
+ ) +} + +function Filter({ + column, + table, +}: { + column: Column + table: PreactTable +}) { + const firstValue = table + .getPreFilteredRowModel() + .flatRows[0]?.getValue(column.id) + + return ( + + {() => + typeof firstValue === 'number' ? ( +
+ + column.setFilterValue((old: any) => [ + (e.target as HTMLInputElement).value, + old?.[1], + ]) + } + placeholder={`Min`} + className="filter-input" + /> + + column.setFilterValue((old: any) => [ + old?.[0], + (e.target as HTMLInputElement).value, + ]) + } + placeholder={`Max`} + className="filter-input" + /> +
+ ) : ( + + column.setFilterValue((e.target as HTMLInputElement).value) + } + placeholder={`Search...`} + className="filter-select" + /> + ) + } +
+ ) +} + +function IndeterminateCheckbox({ + indeterminate, + className = '', + checked, + onChange, + disabled, + ...rest +}: { + indeterminate?: boolean + checked?: boolean + disabled?: boolean + onChange?: (event: Event) => void +} & Record) { + const ref = useRef(null!) + + useEffect(() => { + if (typeof indeterminate === 'boolean') { + ref.current.indeterminate = !checked && indeterminate + } + }, [ref, indeterminate, checked]) + + return ( + + ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render( + <> + + + , + rootElement, +) diff --git a/examples/preact/basic-subscribe/src/makeData.ts b/examples/preact/basic-subscribe/src/makeData.ts new file mode 100644 index 0000000000..c34c43a03e --- /dev/null +++ b/examples/preact/basic-subscribe/src/makeData.ts @@ -0,0 +1,50 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + id: string + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + id: faker.string.uuid(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/qwik/row-selection/src/vite-env.d.ts b/examples/preact/basic-subscribe/src/vite-env.d.ts similarity index 100% rename from examples/qwik/row-selection/src/vite-env.d.ts rename to examples/preact/basic-subscribe/src/vite-env.d.ts diff --git a/examples/preact/basic-subscribe/tsconfig.json b/examples/preact/basic-subscribe/tsconfig.json new file mode 100644 index 0000000000..c1e0bb7b6c --- /dev/null +++ b/examples/preact/basic-subscribe/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/basic-subscribe/vite.config.ts b/examples/preact/basic-subscribe/vite.config.ts new file mode 100644 index 0000000000..c16c7375d0 --- /dev/null +++ b/examples/preact/basic-subscribe/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact({ babel: {} })], +}) diff --git a/examples/preact/basic-use-app-table/index.html b/examples/preact/basic-use-app-table/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/basic-use-app-table/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/basic-use-app-table/package.json b/examples/preact/basic-use-app-table/package.json new file mode 100644 index 0000000000..42575cf0dc --- /dev/null +++ b/examples/preact/basic-use-app-table/package.json @@ -0,0 +1,22 @@ +{ + "name": "tanstack-preact-table-example-basic-use-app-table", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@tanstack/preact-table": "^9.0.0-alpha.45", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/preact/basic-use-app-table/src/index.css b/examples/preact/basic-use-app-table/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/preact/basic-use-app-table/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/preact/basic-use-app-table/src/main.tsx b/examples/preact/basic-use-app-table/src/main.tsx new file mode 100644 index 0000000000..4676e181b7 --- /dev/null +++ b/examples/preact/basic-use-app-table/src/main.tsx @@ -0,0 +1,169 @@ +import { useReducer, useState } from 'preact/hooks' +import { render } from 'preact' +import { createTableHook } from '@tanstack/preact-table' +import './index.css' + +// This example uses the new `createTableHook` method to create a re-usable table hook factory instead of independently using the standalone `useTable` hook and `createColumnHelper` method. You can choose to use either way. + +// 1. Define what the shape of your data will be for each row +type Person = { + firstName: string + lastName: string + age: number + visits: number + status: string + progress: number +} + +// 2. Create some dummy data with a stable reference (this could be an API response stored in useState or similar) +const defaultData: Array = [ + { + firstName: 'tanner', + lastName: 'linsley', + age: 24, + visits: 100, + status: 'In Relationship', + progress: 50, + }, + { + firstName: 'tandy', + lastName: 'miller', + age: 40, + visits: 40, + status: 'Single', + progress: 80, + }, + { + firstName: 'joe', + lastName: 'dirte', + age: 45, + visits: 20, + status: 'Complicated', + progress: 10, + }, + { + firstName: 'kevin', + lastName: 'vandy', + age: 28, + visits: 100, + status: 'Single', + progress: 70, + }, +] + +// 3. New in V9! Tell the table which features and row models we want to use. In this case, this will be a basic table with no additional features +const { useAppTable, createAppColumnHelper } = createTableHook({ + _features: {}, + _rowModels: {}, // client-side row models. `Core` row model is now included by default, but you can still override it here + debugTable: true, +}) + +// 4. Create a helper object to help define our columns +const columnHelper = createAppColumnHelper() + +// 5. Define the columns for your table with a stable reference (in this case, defined statically outside of a react component) +const columns = columnHelper.columns([ + // accessorKey method (most common for simple use-cases) + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (info) => info.column.id, + }), + // accessorFn used (alternative) along with a custom id + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => {info.getValue()}, + header: () => Last Name, + footer: (info) => info.column.id, + }), + // accessorFn used to transform the data + columnHelper.accessor((row) => Number(row.age), { + id: 'age', + header: () => 'Age', + cell: (info) => info.renderValue(), + footer: (info) => info.column.id, + }), + columnHelper.accessor('visits', { + header: () => Visits, + footer: (info) => info.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (info) => info.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (info) => info.column.id, + }), +]) + +function App() { + // 6. Store data with a stable reference + const [data, _setData] = useState(() => [...defaultData]) + const rerender = useReducer(() => ({}), {})[1] + + // 7. Create the table instance with the required columns and data. + // Features and row models are already defined in the createTableHook call above + const table = useAppTable( + { + debugTable: true, + columns, + data, + // add additional table options here or in the createTableHook call above + }, + (state) => state, // default selector + ) + + // 8. Render your table markup from the table instance APIs + return ( +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getAllCells().map((cell) => ( + + ))} + + ))} + + + {table.getFooterGroups().map((footerGroup) => ( + + {footerGroup.headers.map((header) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( + + )} +
+ +
+ {header.isPlaceholder ? null : ( + + )} +
+
+ +
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render(, rootElement) diff --git a/examples/qwik/sorting/src/vite-env.d.ts b/examples/preact/basic-use-app-table/src/vite-env.d.ts similarity index 100% rename from examples/qwik/sorting/src/vite-env.d.ts rename to examples/preact/basic-use-app-table/src/vite-env.d.ts diff --git a/examples/preact/basic-use-app-table/tsconfig.json b/examples/preact/basic-use-app-table/tsconfig.json new file mode 100644 index 0000000000..c1e0bb7b6c --- /dev/null +++ b/examples/preact/basic-use-app-table/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/basic-use-app-table/vite.config.ts b/examples/preact/basic-use-app-table/vite.config.ts new file mode 100644 index 0000000000..29b326faf0 --- /dev/null +++ b/examples/preact/basic-use-app-table/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact()], +}) diff --git a/examples/qwik/basic/.gitignore b/examples/preact/basic-use-table/.gitignore similarity index 100% rename from examples/qwik/basic/.gitignore rename to examples/preact/basic-use-table/.gitignore diff --git a/examples/preact/basic-use-table/README.md b/examples/preact/basic-use-table/README.md new file mode 100644 index 0000000000..56ba98d6c5 --- /dev/null +++ b/examples/preact/basic-use-table/README.md @@ -0,0 +1,15 @@ +# `create-preact` + +

+ +

+ +

Get started using Preact and Vite!

+ +## Getting Started + +- `npm run dev` - Starts a dev server at http://localhost:5173/ + +- `npm run build` - Builds for production, emitting to `dist/` + +- `npm run preview` - Starts a server at http://localhost:4173/ to test production build locally diff --git a/examples/preact/basic-use-table/index.html b/examples/preact/basic-use-table/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/basic-use-table/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/basic-use-table/package.json b/examples/preact/basic-use-table/package.json new file mode 100644 index 0000000000..f46337984d --- /dev/null +++ b/examples/preact/basic-use-table/package.json @@ -0,0 +1,22 @@ +{ + "name": "tanstack-preact-table-example-basic-use-table", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@tanstack/preact-table": "^9.0.0-alpha.45", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/preact/basic-use-table/public/vite.svg b/examples/preact/basic-use-table/public/vite.svg new file mode 100644 index 0000000000..ffcb6bcf53 --- /dev/null +++ b/examples/preact/basic-use-table/public/vite.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/examples/preact/basic-use-table/src/index.css b/examples/preact/basic-use-table/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/preact/basic-use-table/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/preact/basic-use-table/src/main.tsx b/examples/preact/basic-use-table/src/main.tsx new file mode 100644 index 0000000000..9ac3c50d32 --- /dev/null +++ b/examples/preact/basic-use-table/src/main.tsx @@ -0,0 +1,164 @@ +import { render } from 'preact' +import { useReducer, useState } from 'preact/hooks' +import { tableFeatures, useTable } from '@tanstack/preact-table' +import type { ColumnDef } from '@tanstack/preact-table' +import './index.css' + +// This example uses the classic standalone `useTable` hook to create a table without the new `createTableHelper` util. + +// 1. Define what the shape of your data will be for each row +type Person = { + firstName: string + lastName: string + age: number + visits: number + status: string + progress: number +} + +// 2. Create some dummy data with a stable reference (this could be an API response stored in useState or similar) +const defaultData: Array = [ + { + firstName: 'tanner', + lastName: 'linsley', + age: 24, + visits: 100, + status: 'In Relationship', + progress: 50, + }, + { + firstName: 'tandy', + lastName: 'miller', + age: 40, + visits: 40, + status: 'Single', + progress: 80, + }, + { + firstName: 'joe', + lastName: 'dirte', + age: 45, + visits: 20, + status: 'Complicated', + progress: 10, + }, + { + firstName: 'kevin', + lastName: 'vandy', + age: 12, + visits: 100, + status: 'Single', + progress: 70, + }, +] + +// 3. New in V9! Tell the table which features and row models we want to use. In this case, this will be a basic table with no additional features +const _features = tableFeatures({}) // util method to create sharable TFeatures object/type + +// 4. Define the columns for your table. This uses the new `ColumnDef` type to define columns. Alternatively, check out the createTableHelper/createColumnHelper util for an even more type-safe way to define columns. +const columns: Array> = [ + { + accessorKey: 'firstName', // accessorKey method (most common for simple use-cases) + header: 'First Name', + cell: (info) => info.getValue(), + }, + { + accessorFn: (row) => row.lastName, // accessorFn used (alternative) along with a custom id + id: 'lastName', + header: () => Last Name, + cell: (info) => {info.getValue()}, + }, + { + accessorFn: (row) => Number(row.age), // accessorFn used to transform the data + id: 'age', + header: () => 'Age', + cell: (info) => { + return info.renderValue() + }, + }, + { + accessorKey: 'visits', + header: () => Visits, + }, + { + accessorKey: 'status', + header: 'Status', + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + }, +] + +function App() { + // 5. Store data with a stable reference + const [data, _setData] = useState(() => [...defaultData]) + const rerender = useReducer(() => ({}), {})[1] + + // 6. Create the table instance with required _features, columns, and data + const table = useTable( + { + debugTable: true, + _features, // new required option in V9. Tell the table which features you are importing and using (better tree-shaking) + _rowModels: {}, // `Core` row model is now included by default, but you can still override it here + columns, + data, + // add additional table options here + }, + (state) => state, // default selector + ) + + // 7. Render your table markup from the table instance APIs + return ( +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getAllCells().map((cell) => ( + + ))} + + ))} + + + {table.getFooterGroups().map((footerGroup) => ( + + {footerGroup.headers.map((header) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( + + )} +
+ +
+ {header.isPlaceholder ? null : ( + + )} +
+
+ +
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render(, rootElement) diff --git a/examples/vue/basic/env.d.ts b/examples/preact/basic-use-table/src/vite-env.d.ts similarity index 100% rename from examples/vue/basic/env.d.ts rename to examples/preact/basic-use-table/src/vite-env.d.ts diff --git a/examples/preact/basic-use-table/tsconfig.json b/examples/preact/basic-use-table/tsconfig.json new file mode 100644 index 0000000000..c1e0bb7b6c --- /dev/null +++ b/examples/preact/basic-use-table/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/basic-use-table/vite.config.ts b/examples/preact/basic-use-table/vite.config.ts new file mode 100644 index 0000000000..29b326faf0 --- /dev/null +++ b/examples/preact/basic-use-table/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact()], +}) diff --git a/examples/preact/column-groups/index.html b/examples/preact/column-groups/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/column-groups/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/column-groups/package.json b/examples/preact/column-groups/package.json new file mode 100644 index 0000000000..074ce77f3b --- /dev/null +++ b/examples/preact/column-groups/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-preact-table-example-column-groups", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/preact-table": "^9.0.0-alpha.45", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/preact/column-groups/src/index.css b/examples/preact/column-groups/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/preact/column-groups/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/preact/column-groups/src/main.tsx b/examples/preact/column-groups/src/main.tsx new file mode 100644 index 0000000000..6a78c373f8 --- /dev/null +++ b/examples/preact/column-groups/src/main.tsx @@ -0,0 +1,136 @@ +import { useReducer, useState } from 'preact/hooks' +import { render } from 'preact' +import './index.css' +import { + createColumnHelper, + tableFeatures, + useTable, +} from '@tanstack/preact-table' +import { makeData } from './makeData' +import type { Person } from './makeData' + +const _features = tableFeatures({}) + +const columnHelper = createColumnHelper() + +// use new columnHelper.columns method to create columns with the same TValue generic so TypeScript doesn't complain when passing columns to useTable +const columns = columnHelper.columns([ + columnHelper.group({ + id: 'hello', + header: () => Hello, + columns: columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }), + ]), + }), + columnHelper.group({ + header: 'Info', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + }), + columnHelper.group({ + header: 'More Info', + columns: columnHelper.columns([ + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), + ]), + }), + ]), + }), +]) + +function App() { + const [data, setData] = useState(() => makeData(20)) + const refreshData = () => setData(makeData(20)) + const stressTest = () => setData(makeData(1_000)) + const rerender = useReducer(() => ({}), {})[1] + + const table = useTable( + { + debugTable: true, + _features, + _rowModels: {}, + columns, + data, + }, + (state) => state, // default selector + ) + + return ( +
+
+ + +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getAllCells().map((cell) => ( + + ))} + + ))} + + + {table.getFooterGroups().map((footerGroup) => ( + + {footerGroup.headers.map((header) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( + + )} +
+ +
+ {header.isPlaceholder ? null : ( + + )} +
+
+ +
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render(, rootElement) diff --git a/examples/preact/column-groups/src/makeData.ts b/examples/preact/column-groups/src/makeData.ts new file mode 100644 index 0000000000..c0f87b8b19 --- /dev/null +++ b/examples/preact/column-groups/src/makeData.ts @@ -0,0 +1,46 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + status: string + progress: number +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + } + }) + } + + return makeDataLevel() +} diff --git a/examples/vue/pagination-controlled/env.d.ts b/examples/preact/column-groups/src/vite-env.d.ts similarity index 100% rename from examples/vue/pagination-controlled/env.d.ts rename to examples/preact/column-groups/src/vite-env.d.ts diff --git a/examples/preact/column-groups/tsconfig.json b/examples/preact/column-groups/tsconfig.json new file mode 100644 index 0000000000..c1e0bb7b6c --- /dev/null +++ b/examples/preact/column-groups/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/column-groups/vite.config.ts b/examples/preact/column-groups/vite.config.ts new file mode 100644 index 0000000000..29b326faf0 --- /dev/null +++ b/examples/preact/column-groups/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact()], +}) diff --git a/examples/preact/column-ordering/index.html b/examples/preact/column-ordering/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/column-ordering/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/column-ordering/package.json b/examples/preact/column-ordering/package.json new file mode 100644 index 0000000000..5ff475a23d --- /dev/null +++ b/examples/preact/column-ordering/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-preact-table-example-column-ordering", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/preact-table": "^9.0.0-alpha.45", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/preact/column-ordering/src/index.css b/examples/preact/column-ordering/src/index.css new file mode 100644 index 0000000000..efd0b26417 --- /dev/null +++ b/examples/preact/column-ordering/src/index.css @@ -0,0 +1,374 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td { + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td:last-child { + border-right: 0; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/preact/column-ordering/src/main.tsx b/examples/preact/column-ordering/src/main.tsx new file mode 100644 index 0000000000..a63f5707a1 --- /dev/null +++ b/examples/preact/column-ordering/src/main.tsx @@ -0,0 +1,191 @@ +import { useState } from 'preact/hooks' +import { render } from 'preact' +import { faker } from '@faker-js/faker' +import { + columnOrderingFeature, + columnVisibilityFeature, + createColumnHelper, + tableFeatures, + useTable, +} from '@tanstack/preact-table' +import { makeData } from './makeData' +import type { Person } from './makeData' +import './index.css' + +const _features = tableFeatures({ + columnOrderingFeature, + columnVisibilityFeature, +}) + +const columnHelper = createColumnHelper() + +const defaultColumns = columnHelper.columns([ + columnHelper.group({ + header: 'Name', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }), + ]), + }), + columnHelper.group({ + header: 'Info', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + }), + columnHelper.group({ + header: 'More Info', + columns: columnHelper.columns([ + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), + ]), + }), + ]), + }), +]) + +function App() { + const [data, setData] = useState(() => makeData(20)) + const [columns] = useState(() => [...defaultColumns]) + + const refreshData = () => setData(() => makeData(20)) + const stressTest = () => setData(() => makeData(1_000)) + + const table = useTable( + { + _features, + _rowModels: {}, + columns, + data, + debugTable: true, + debugHeaders: true, + debugColumns: true, + }, + (state) => state, // default selector + ) + + const randomizeColumns = () => { + table.setColumnOrder( + faker.helpers.shuffle(table.getAllLeafColumns().map((d) => d.id)), + ) + } + + return ( +
+
+
+ +
+ {table.getAllLeafColumns().map((column) => { + return ( +
+ +
+ ) + })} +
+
+
+ + + +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + ))} + + ))} + + + {table.getFooterGroups().map((footerGroup) => ( + + {footerGroup.headers.map((header) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( + + )} +
+ +
+ {header.isPlaceholder ? null : ( + + )} +
+
{JSON.stringify(table.state, null, 2)}
+
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render(, rootElement) diff --git a/examples/preact/column-ordering/src/makeData.ts b/examples/preact/column-ordering/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/preact/column-ordering/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/preact/column-ordering/src/vite-env.d.ts b/examples/preact/column-ordering/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/preact/column-ordering/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/preact/column-ordering/tsconfig.json b/examples/preact/column-ordering/tsconfig.json new file mode 100644 index 0000000000..c1e0bb7b6c --- /dev/null +++ b/examples/preact/column-ordering/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/column-ordering/vite.config.ts b/examples/preact/column-ordering/vite.config.ts new file mode 100644 index 0000000000..29b326faf0 --- /dev/null +++ b/examples/preact/column-ordering/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact()], +}) diff --git a/examples/preact/column-pinning-split/index.html b/examples/preact/column-pinning-split/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/column-pinning-split/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/column-pinning-split/package.json b/examples/preact/column-pinning-split/package.json new file mode 100644 index 0000000000..816fd68804 --- /dev/null +++ b/examples/preact/column-pinning-split/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-preact-table-example-column-pinning-split", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/preact-table": "^9.0.0-alpha.45", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/preact/column-pinning-split/src/index.css b/examples/preact/column-pinning-split/src/index.css new file mode 100644 index 0000000000..efd0b26417 --- /dev/null +++ b/examples/preact/column-pinning-split/src/index.css @@ -0,0 +1,374 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td { + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td:last-child { + border-right: 0; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/preact/column-pinning-split/src/main.tsx b/examples/preact/column-pinning-split/src/main.tsx new file mode 100644 index 0000000000..ee63585bd9 --- /dev/null +++ b/examples/preact/column-pinning-split/src/main.tsx @@ -0,0 +1,366 @@ +import { useState } from 'preact/hooks' +import { render } from 'preact' +import { faker } from '@faker-js/faker' +import './index.css' +import { + columnOrderingFeature, + columnPinningFeature, + columnVisibilityFeature, + createColumnHelper, + tableFeatures, + useTable, +} from '@tanstack/preact-table' +import { makeData } from './makeData' +import type { Person } from './makeData' + +const _features = tableFeatures({ + columnVisibilityFeature, + columnPinningFeature, + columnOrderingFeature, +}) + +const columnHelper = createColumnHelper() +const defaultColumns = columnHelper.columns([ + columnHelper.group({ + header: 'Name', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }), + ]), + }), + columnHelper.group({ + header: 'Info', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + }), + columnHelper.group({ + header: 'More Info', + columns: columnHelper.columns([ + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), + ]), + }), + ]), + }), +]) + +function App() { + const [data, setData] = useState(() => makeData(1_000)) + const [columns] = useState(() => [...defaultColumns]) + + const refreshData = () => setData(() => makeData(1_000)) + const stressTest = () => setData(() => makeData(500_000)) + + const table = useTable( + { + _features, + _rowModels: {}, + columns, + data, + debugTable: true, + debugHeaders: true, + debugColumns: true, + }, + (state) => state, // default selector + ) + + const randomizeColumns = () => { + table.setColumnOrder( + faker.helpers.shuffle(table.getAllLeafColumns().map((d) => d.id)), + ) + } + + return ( +
+
+
+ +
+ {table.getAllLeafColumns().map((column) => { + return ( +
+ +
+ ) + })} +
+
+
+ + + +
+
+

+ This example takes advantage of the "splitting" APIs. (APIs that have + "left, "center", and "right" modifiers) +

+
+ + + {table.getLeftHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table + .getRowModel() + .rows.slice(0, 20) + .map((row) => { + return ( + + {row.getLeftVisibleCells().map((cell) => { + return ( + + ) + })} + + ) + })} + +
+
+ {header.isPlaceholder ? null : ( + + )} +
+ {!header.isPlaceholder && header.column.getCanPin() && ( +
+ {header.column.getIsPinned() !== 'left' ? ( + + ) : null} + {header.column.getIsPinned() ? ( + + ) : null} + {header.column.getIsPinned() !== 'right' ? ( + + ) : null} +
+ )} +
+ +
+ + + {table.getCenterHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table + .getRowModel() + .rows.slice(0, 20) + .map((row) => { + return ( + + {row.getCenterVisibleCells().map((cell) => { + return ( + + ) + })} + + ) + })} + +
+
+ {header.isPlaceholder ? null : ( + + )} +
+ {!header.isPlaceholder && header.column.getCanPin() && ( +
+ {header.column.getIsPinned() !== 'left' ? ( + + ) : null} + {header.column.getIsPinned() ? ( + + ) : null} + {header.column.getIsPinned() !== 'right' ? ( + + ) : null} +
+ )} +
+ +
+ + + {table.getRightHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table + .getRowModel() + .rows.slice(0, 20) + .map((row) => { + return ( + + {row.getRightVisibleCells().map((cell) => { + return ( + + ) + })} + + ) + })} + +
+
+ {header.isPlaceholder ? null : ( + + )} +
+ {!header.isPlaceholder && header.column.getCanPin() && ( +
+ {header.column.getIsPinned() !== 'left' ? ( + + ) : null} + {header.column.getIsPinned() ? ( + + ) : null} + {header.column.getIsPinned() !== 'right' ? ( + + ) : null} +
+ )} +
+ +
+
+
{JSON.stringify(table.state, null, 2)}
+
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render(, rootElement) diff --git a/examples/preact/column-pinning-split/src/makeData.ts b/examples/preact/column-pinning-split/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/preact/column-pinning-split/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/preact/column-pinning-split/src/vite-env.d.ts b/examples/preact/column-pinning-split/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/preact/column-pinning-split/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/preact/column-pinning-split/tsconfig.json b/examples/preact/column-pinning-split/tsconfig.json new file mode 100644 index 0000000000..c1e0bb7b6c --- /dev/null +++ b/examples/preact/column-pinning-split/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/column-pinning-split/vite.config.ts b/examples/preact/column-pinning-split/vite.config.ts new file mode 100644 index 0000000000..29b326faf0 --- /dev/null +++ b/examples/preact/column-pinning-split/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact()], +}) diff --git a/examples/preact/column-pinning-sticky/index.html b/examples/preact/column-pinning-sticky/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/column-pinning-sticky/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/column-pinning-sticky/package.json b/examples/preact/column-pinning-sticky/package.json new file mode 100644 index 0000000000..1cff7b013c --- /dev/null +++ b/examples/preact/column-pinning-sticky/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-preact-table-example-column-pinning-sticky", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/preact-table": "^9.0.0-alpha.45", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/preact/column-pinning-sticky/src/index.css b/examples/preact/column-pinning-sticky/src/index.css new file mode 100644 index 0000000000..a6f4017012 --- /dev/null +++ b/examples/preact/column-pinning-sticky/src/index.css @@ -0,0 +1,389 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +.table-container { + border: 1px solid lightgray; + overflow-x: scroll; + width: 100%; + max-width: 960px; + position: relative; + border-collapse: collapse; + border-spacing: 0; +} + +table { + /* box-shadow and borders will not work with positon: sticky otherwise */ + border-collapse: collapse; + border-spacing: 0; +} + +th { + background-color: lightgray; + border-bottom: 1px solid lightgray; + font-weight: bold; + height: 30px; + padding: 2px 4px; + position: relative; + text-align: center; +} + +td { + background-color: white; + padding: 2px 4px; +} + +.resizer { + background: rgba(0, 0, 0, 0.5); + cursor: col-resize; + height: 100%; + position: absolute; + right: 0; + top: 0; + touch-action: none; + user-select: none; + width: 5px; +} + +.resizer.isResizing { + background: blue; + opacity: 1; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/preact/column-pinning-sticky/src/main.tsx b/examples/preact/column-pinning-sticky/src/main.tsx new file mode 100644 index 0000000000..ec747e398a --- /dev/null +++ b/examples/preact/column-pinning-sticky/src/main.tsx @@ -0,0 +1,278 @@ +import { useState } from 'preact/hooks' +import { render } from 'preact' +import { + columnOrderingFeature, + columnPinningFeature, + columnResizingFeature, + columnSizingFeature, + columnVisibilityFeature, + createColumnHelper, + tableFeatures, + useTable, +} from '@tanstack/preact-table' +import { faker } from '@faker-js/faker' +import { makeData } from './makeData' +import type { Column } from '@tanstack/preact-table' +import type { CSSProperties } from 'preact/compat' +import type { Person } from './makeData' +import './index.css' + +const _features = tableFeatures({ + columnOrderingFeature, + columnPinningFeature, + columnResizingFeature, + columnSizingFeature, + columnVisibilityFeature, +}) + +const columnHelper = createColumnHelper() +// These are the important styles to make sticky column pinning work! +// Apply styles like this using your CSS strategy of choice with this kind of logic to head cells, data cells, footer cells, etc. +// View the index.css file for more needed styles such as border-collapse: collapse +const getCommonPinningStyles = ( + column: Column, +): CSSProperties => { + const isPinned = column.getIsPinned() + const isLastLeftPinnedColumn = + isPinned === 'left' && column.getIsLastColumn('left') + const isFirstRightPinnedColumn = + isPinned === 'right' && column.getIsFirstColumn('right') + + return { + boxShadow: isLastLeftPinnedColumn + ? '-4px 0 4px -4px gray inset' + : isFirstRightPinnedColumn + ? '4px 0 4px -4px gray inset' + : undefined, + left: isPinned === 'left' ? `${column.getStart('left')}px` : undefined, + right: isPinned === 'right' ? `${column.getAfter('right')}px` : undefined, + opacity: isPinned ? 0.95 : 1, + position: isPinned ? 'sticky' : 'relative', + width: column.getSize(), + zIndex: isPinned ? 1 : 0, + } +} + +const defaultColumns = columnHelper.columns([ + columnHelper.accessor('firstName', { + id: 'firstName', + header: 'First Name', + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + size: 180, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + size: 180, + }), + columnHelper.accessor('age', { + id: 'age', + header: 'Age', + footer: (props) => props.column.id, + size: 180, + }), + columnHelper.accessor('visits', { + id: 'visits', + header: 'Visits', + footer: (props) => props.column.id, + size: 180, + }), + columnHelper.accessor('status', { + id: 'status', + header: 'Status', + footer: (props) => props.column.id, + size: 180, + }), + columnHelper.accessor('progress', { + id: 'progress', + header: 'Profile Progress', + footer: (props) => props.column.id, + size: 180, + }), +]) + +function App() { + const [data, setData] = useState(() => makeData(20)) + const [columns] = useState(() => [...defaultColumns]) + + const refreshData = () => setData(() => makeData(20)) + const stressTest = () => setData(() => makeData(1_000)) + + const table = useTable( + { + _features, + _rowModels: {}, + columns, + data, + debugTable: true, + debugHeaders: true, + debugColumns: true, + columnResizeMode: 'onChange', + }, + (state) => state, // default selector + ) + + const randomizeColumns = () => { + table.setColumnOrder( + faker.helpers.shuffle(table.getAllLeafColumns().map((d) => d.id)), + ) + } + + return ( +
+
+
+ +
+ {table.getAllLeafColumns().map((column) => { + return ( +
+ +
+ ) + })} +
+
+
+ + + +
+
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + const { column } = header + + return ( + + ) + })} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => { + const { column } = cell + return ( + + ) + })} + + ))} + +
+
+ {header.isPlaceholder ? null : ( + <> + {' '} + + )} + {/* Demo getIndex behavior */} + {column.getIndex(column.getIsPinned() || 'center')} +
+ {!header.isPlaceholder && header.column.getCanPin() && ( +
+ {header.column.getIsPinned() !== 'left' ? ( + + ) : null} + {header.column.getIsPinned() ? ( + + ) : null} + {header.column.getIsPinned() !== 'right' ? ( + + ) : null} +
+ )} +
header.column.resetSize()} + onMouseDown={header.getResizeHandler()} + onTouchStart={header.getResizeHandler()} + className={`resizer ${ + header.column.getIsResizing() ? 'isResizing' : '' + }`} + /> +
+ +
+
+
{JSON.stringify(table.state, null, 2)}
+
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render(, rootElement) diff --git a/examples/preact/column-pinning-sticky/src/makeData.ts b/examples/preact/column-pinning-sticky/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/preact/column-pinning-sticky/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/preact/column-pinning-sticky/src/vite-env.d.ts b/examples/preact/column-pinning-sticky/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/preact/column-pinning-sticky/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/preact/column-pinning-sticky/tsconfig.json b/examples/preact/column-pinning-sticky/tsconfig.json new file mode 100644 index 0000000000..c1e0bb7b6c --- /dev/null +++ b/examples/preact/column-pinning-sticky/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/column-pinning-sticky/vite.config.ts b/examples/preact/column-pinning-sticky/vite.config.ts new file mode 100644 index 0000000000..29b326faf0 --- /dev/null +++ b/examples/preact/column-pinning-sticky/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact()], +}) diff --git a/examples/preact/column-pinning/index.html b/examples/preact/column-pinning/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/column-pinning/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/column-pinning/package.json b/examples/preact/column-pinning/package.json new file mode 100644 index 0000000000..12ec3cb70a --- /dev/null +++ b/examples/preact/column-pinning/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-preact-table-example-column-pinning", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/preact-table": "^9.0.0-alpha.45", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/preact/column-pinning/src/index.css b/examples/preact/column-pinning/src/index.css new file mode 100644 index 0000000000..efd0b26417 --- /dev/null +++ b/examples/preact/column-pinning/src/index.css @@ -0,0 +1,374 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td { + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td:last-child { + border-right: 0; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/preact/column-pinning/src/main.tsx b/examples/preact/column-pinning/src/main.tsx new file mode 100644 index 0000000000..303b21b4ab --- /dev/null +++ b/examples/preact/column-pinning/src/main.tsx @@ -0,0 +1,231 @@ +import { useState } from 'preact/hooks' +import { render } from 'preact' +import { faker } from '@faker-js/faker' +import './index.css' +import { + columnOrderingFeature, + columnPinningFeature, + columnVisibilityFeature, + createTableHook, +} from '@tanstack/preact-table' +import { makeData } from './makeData' +import type { Person } from './makeData' + +// Create table hook with features +const { useAppTable, createAppColumnHelper } = createTableHook({ + _features: { + columnVisibilityFeature, + columnPinningFeature, + columnOrderingFeature, + }, + _rowModels: {}, + debugTable: true, + debugHeaders: true, + debugColumns: true, +}) + +// Create column helper +const columnHelper = createAppColumnHelper() + +// Define columns using columnHelper +const columns = columnHelper.columns([ + columnHelper.group({ + header: 'Name', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }), + ]), + }), + columnHelper.group({ + header: 'Info', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + }), + columnHelper.group({ + header: 'More Info', + columns: columnHelper.columns([ + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), + ]), + }), + ]), + }), +]) + +function App() { + const [data, setData] = useState(() => makeData(1_000)) + + const refreshData = () => setData(() => makeData(1_000)) + const stressTest = () => setData(() => makeData(500_000)) + + const table = useAppTable( + { + debugTable: true, + columns, + data, + }, + (state) => state, // default selector + ) + + const randomizeColumns = () => { + table.setColumnOrder( + faker.helpers.shuffle(table.getAllLeafColumns().map((d) => d.id)), + ) + } + + return ( +
+
+
+ +
+ {table.getAllLeafColumns().map((column) => { + return ( +
+ +
+ ) + })} +
+
+
+ + + +
+
+

+ This example using the non-split APIs. Columns are just reordered within + 1 table instead of being split into 3 different tables. +

+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table + .getRowModel() + .rows.slice(0, 20) + .map((row) => { + return ( + + {row.getVisibleCells().map((cell) => { + return ( + + ) + })} + + ) + })} + +
+
+ {header.isPlaceholder ? null : ( + + )} +
+ {!header.isPlaceholder && header.column.getCanPin() && ( +
+ {header.column.getIsPinned() !== 'left' ? ( + + ) : null} + {header.column.getIsPinned() ? ( + + ) : null} + {header.column.getIsPinned() !== 'right' ? ( + + ) : null} +
+ )} +
+ +
+
+
{JSON.stringify(table.state, null, 2)}
+
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render(, rootElement) diff --git a/examples/preact/column-pinning/src/makeData.ts b/examples/preact/column-pinning/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/preact/column-pinning/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/preact/column-pinning/src/vite-env.d.ts b/examples/preact/column-pinning/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/preact/column-pinning/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/preact/column-pinning/tsconfig.json b/examples/preact/column-pinning/tsconfig.json new file mode 100644 index 0000000000..c1e0bb7b6c --- /dev/null +++ b/examples/preact/column-pinning/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/column-pinning/vite.config.ts b/examples/preact/column-pinning/vite.config.ts new file mode 100644 index 0000000000..29b326faf0 --- /dev/null +++ b/examples/preact/column-pinning/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact()], +}) diff --git a/examples/preact/column-resizing-performant/index.html b/examples/preact/column-resizing-performant/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/column-resizing-performant/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/column-resizing-performant/package.json b/examples/preact/column-resizing-performant/package.json new file mode 100644 index 0000000000..b34bcc1f53 --- /dev/null +++ b/examples/preact/column-resizing-performant/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-preact-table-example-column-resizing-performant", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/preact-table": "^9.0.0-alpha.45", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/preact/column-resizing-performant/src/index.css b/examples/preact/column-resizing-performant/src/index.css new file mode 100644 index 0000000000..d8ee293049 --- /dev/null +++ b/examples/preact/column-resizing-performant/src/index.css @@ -0,0 +1,412 @@ +* { + box-sizing: border-box; +} + +html { + font-family: sans-serif; + font-size: 14px; +} + +table, +.divTable { + border: 1px solid lightgray; + width: fit-content; + border-collapse: collapse; + border-spacing: 0; +} + +.tr { + display: flex; +} + +tr, +.tr { + width: fit-content; + height: 30px; +} + +th, +.th, +td, +.td { + box-shadow: inset 0 0 0 1px lightgray; + padding: 0.25rem; +} + +th, +.th { + padding: 2px 4px; + position: relative; + font-weight: bold; + text-align: center; + height: 30px; +} + +td, +.td { + height: 30px; +} + +.resizer { + position: absolute; + top: 0; + height: 100%; + right: 0; + width: 5px; + background: rgba(0, 0, 0, 0.5); + cursor: col-resize; + user-select: none; + touch-action: none; +} + +.resizer.isResizing { + background: blue; + opacity: 1; +} + +@media (hover: hover) { + .resizer { + opacity: 0; + } + + *:hover > .resizer { + opacity: 1; + } +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/preact/column-resizing-performant/src/main.tsx b/examples/preact/column-resizing-performant/src/main.tsx new file mode 100644 index 0000000000..4993831758 --- /dev/null +++ b/examples/preact/column-resizing-performant/src/main.tsx @@ -0,0 +1,231 @@ +import { useMemo, useReducer, useState } from 'preact/hooks' +import { memo } from 'preact/compat' +import { render } from 'preact' +import { + columnResizingFeature, + columnSizingFeature, + createColumnHelper, + tableFeatures, + useTable, +} from '@tanstack/preact-table' +import { makeData } from './makeData' +import type { Table } from '@tanstack/preact-table' +import './index.css' + +const _features = tableFeatures({ columnSizingFeature, columnResizingFeature }) + +type Person = { + firstName: string + lastName: string + age: number + visits: number + status: string + progress: number +} + +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.group({ + header: 'Name', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }), + ]), + }), + columnHelper.group({ + header: 'Info', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + }), + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), + ]), + }), +]) + +function App() { + const [data, setData] = useState(() => makeData(200)) + const refreshData = () => setData(makeData(200)) + const stressTest = () => setData(makeData(2_000)) + + const rerender = useReducer(() => ({}), {})[1] + + const table = useTable( + { + _features, + _rowModels: {}, + columns, + data, + defaultColumn: { + minSize: 60, + maxSize: 800, + }, + columnResizeMode: 'onChange', + debugTable: true, + debugHeaders: true, + debugColumns: true, + }, + (state) => ({ + columnSizing: state.columnSizing, + columnResizing: state.columnResizing, + }), + ) + + /** + * Instead of calling `column.getSize()` on every render for every header + * and especially every data cell (very expensive), + * we will calculate all column sizes at once at the root table level in a useMemo + * and pass the column sizes down as CSS variables to the element. + */ + const columnSizeVars = useMemo(() => { + const headers = table.getFlatHeaders() + const colSizes: { [key: string]: number } = {} + for (const header of headers) { + colSizes[`--header-${header.id}-size`] = header.getSize() + colSizes[`--col-${header.column.id}-size`] = header.column.getSize() + } + return colSizes + }, [table.state.columnResizing, table.state.columnSizing]) + + // demo purposes + const [enableMemo, setEnableMemo] = useState(true) + + return ( +
+
+ + +
+
+ + This example has artificially slow cell renders to simulate complex + usage + +
+ +
+ +
+        {JSON.stringify(table.state, null, 2)}
+      
+
({data.length.toLocaleString()} rows) +
+ {/* Here in the
equivalent element (surrounds all table head and data cells), we will define our CSS variables for column sizes */} +
element + width: table.getTotalSize(), + }} + > +
+ {table.getHeaderGroups().map((headerGroup) => ( +
+ {headerGroup.headers.map((header) => ( +
+ {header.isPlaceholder ? null : ( + + )} +
header.column.resetSize()} + onMouseDown={header.getResizeHandler()} + onTouchStart={header.getResizeHandler()} + className={`resizer ${ + header.column.getIsResizing() ? 'isResizing' : '' + }`} + /> +
+ ))} +
+ ))} +
+ {/* When resizing any column we will render this special memoized version of our table body */} + {table.store.state.columnResizing.isResizingColumn && enableMemo ? ( + + ) : ( + + )} +
+
+ + ) +} + +// un-memoized normal table body component - see memoized version below +function TableBody({ table }: { table: Table }) { + return ( +
+ {table.getRowModel().rows.map((row) => ( +
+ {row.getAllCells().map((cell) => { + // simulate expensive render + for (const _ of Array(10000)) { + Math.random() + } + + return ( +
+ {cell.renderValue()} +
+ ) + })} +
+ ))} +
+ ) +} + +// special memoized wrapper for our table body that we will use during column resizing +export const MemoizedTableBody = memo( + TableBody, + (prev, next) => prev.table.options.data === next.table.options.data, +) + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render(, rootElement) diff --git a/examples/preact/column-resizing-performant/src/makeData.ts b/examples/preact/column-resizing-performant/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/preact/column-resizing-performant/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/preact/column-resizing-performant/src/vite-env.d.ts b/examples/preact/column-resizing-performant/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/preact/column-resizing-performant/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/preact/column-resizing-performant/tsconfig.json b/examples/preact/column-resizing-performant/tsconfig.json new file mode 100644 index 0000000000..c1e0bb7b6c --- /dev/null +++ b/examples/preact/column-resizing-performant/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/column-resizing-performant/vite.config.ts b/examples/preact/column-resizing-performant/vite.config.ts new file mode 100644 index 0000000000..29b326faf0 --- /dev/null +++ b/examples/preact/column-resizing-performant/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact()], +}) diff --git a/examples/preact/column-resizing/index.html b/examples/preact/column-resizing/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/column-resizing/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/column-resizing/package.json b/examples/preact/column-resizing/package.json new file mode 100644 index 0000000000..41c5ffe0d8 --- /dev/null +++ b/examples/preact/column-resizing/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-preact-table-example-column-resizing", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/preact-table": "^9.0.0-alpha.45", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/preact/column-resizing/src/index.css b/examples/preact/column-resizing/src/index.css new file mode 100644 index 0000000000..d7f648b224 --- /dev/null +++ b/examples/preact/column-resizing/src/index.css @@ -0,0 +1,419 @@ +* { + box-sizing: border-box; +} + +html { + font-family: sans-serif; + font-size: 14px; +} + +table, +.divTable { + border: 1px solid lightgray; + width: fit-content; + border-collapse: collapse; + border-spacing: 0; +} + +.tr { + display: flex; +} + +tr, +.tr { + width: fit-content; + height: 30px; +} + +th, +.th, +td, +.td { + box-shadow: inset 0 0 0 1px lightgray; + padding: 0.25rem; +} + +th, +.th { + padding: 2px 4px; + position: relative; + font-weight: bold; + text-align: center; + height: 30px; +} + +td, +.td { + height: 30px; +} + +.resizer { + position: absolute; + top: 0; + height: 100%; + width: 5px; + background: rgba(0, 0, 0, 0.5); + cursor: col-resize; + user-select: none; + touch-action: none; +} + +.resizer.ltr { + right: 0; +} + +.resizer.rtl { + left: 0; +} + +.resizer.isResizing { + background: blue; + opacity: 1; +} + +@media (hover: hover) { + .resizer { + opacity: 0; + } + + *:hover > .resizer { + opacity: 1; + } +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/preact/column-resizing/src/main.tsx b/examples/preact/column-resizing/src/main.tsx new file mode 100644 index 0000000000..8b1c60fe68 --- /dev/null +++ b/examples/preact/column-resizing/src/main.tsx @@ -0,0 +1,349 @@ +import { useReducer, useState } from 'preact/hooks' +import { render } from 'preact' +import { + columnResizingFeature, + columnSizingFeature, + createColumnHelper, + tableFeatures, + useTable, +} from '@tanstack/preact-table' +import { makeData } from './makeData' +import type { Person } from './makeData' +import type { + ColumnResizeDirection, + ColumnResizeMode, +} from '@tanstack/preact-table' +import './index.css' + +const _features = tableFeatures({ columnResizingFeature, columnSizingFeature }) + +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.group({ + header: 'Name', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }), + ]), + }), + columnHelper.group({ + header: 'Info', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + }), + columnHelper.group({ + header: 'More Info', + columns: columnHelper.columns([ + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), + ]), + }), + ]), + }), +]) + +function App() { + const [data, setData] = useState(() => makeData(10)) + const refreshData = () => setData(makeData(10)) + const stressTest = () => setData(makeData(100)) + + const [columnResizeMode, setColumnResizeMode] = + useState('onChange') + + const [columnResizeDirection, setColumnResizeDirection] = + useState('ltr') + + const rerender = useReducer(() => ({}), {})[1] + + const table = useTable( + { + _features, + _rowModels: {}, + columns, + data, + columnResizeMode, + columnResizeDirection, + debugTable: true, + debugHeaders: true, + debugColumns: true, + }, + (state) => state, // default selector + ) + + return ( +
+
+ + +
+ + +
+
+
{'
'} +
+
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getAllCells().map((cell) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( + + )} +
header.column.resetSize()} + onMouseDown={header.getResizeHandler()} + onTouchStart={header.getResizeHandler()} + className={`resizer ${ + table.options.columnResizeDirection + } ${header.column.getIsResizing() ? 'isResizing' : ''}`} + style={{ + transform: + columnResizeMode === 'onEnd' && + header.column.getIsResizing() + ? `translateX(${ + (table.options.columnResizeDirection === 'rtl' + ? -1 + : 1) * + (table.store.state.columnResizing + .deltaOffset ?? 0) + }px)` + : '', + }} + /> +
+ +
+
+
+
{'
(relative)'}
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => ( +
+ {headerGroup.headers.map((header) => ( +
+ {header.isPlaceholder ? null : ( + + )} +
header.column.resetSize()} + onMouseDown={header.getResizeHandler()} + onTouchStart={header.getResizeHandler()} + className={`resizer ${ + table.options.columnResizeDirection + } ${header.column.getIsResizing() ? 'isResizing' : ''}`} + style={{ + transform: + columnResizeMode === 'onEnd' && + header.column.getIsResizing() + ? `translateX(${ + (table.options.columnResizeDirection === 'rtl' + ? -1 + : 1) * + (table.store.state.columnResizing + .deltaOffset ?? 0) + }px)` + : '', + }} + /> +
+ ))} +
+ ))} +
+
+ {table.getRowModel().rows.map((row) => ( +
+ {row.getAllCells().map((cell) => ( +
+ +
+ ))} +
+ ))} +
+
+
+
+
{'
(absolute positioning)'}
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => ( +
+ {headerGroup.headers.map((header) => ( +
+ {header.isPlaceholder ? null : ( + + )} +
header.column.resetSize()} + onMouseDown={header.getResizeHandler()} + onTouchStart={header.getResizeHandler()} + className={`resizer ${ + table.options.columnResizeDirection + } ${header.column.getIsResizing() ? 'isResizing' : ''}`} + style={{ + transform: + columnResizeMode === 'onEnd' && + header.column.getIsResizing() + ? `translateX(${ + (table.options.columnResizeDirection === 'rtl' + ? -1 + : 1) * + (table.store.state.columnResizing + .deltaOffset ?? 0) + }px)` + : '', + }} + /> +
+ ))} +
+ ))} +
+
+ {table.getRowModel().rows.map((row) => ( +
+ {row.getAllCells().map((cell) => ( +
+ +
+ ))} +
+ ))} +
+
+
+
+
+ +
{JSON.stringify(table.state, null, 2)}
+
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render(, rootElement) diff --git a/examples/preact/column-resizing/src/makeData.ts b/examples/preact/column-resizing/src/makeData.ts new file mode 100644 index 0000000000..c0f87b8b19 --- /dev/null +++ b/examples/preact/column-resizing/src/makeData.ts @@ -0,0 +1,46 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + status: string + progress: number +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + } + }) + } + + return makeDataLevel() +} diff --git a/examples/preact/column-resizing/src/vite-env.d.ts b/examples/preact/column-resizing/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/preact/column-resizing/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/preact/column-resizing/tsconfig.json b/examples/preact/column-resizing/tsconfig.json new file mode 100644 index 0000000000..c1e0bb7b6c --- /dev/null +++ b/examples/preact/column-resizing/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/column-resizing/vite.config.ts b/examples/preact/column-resizing/vite.config.ts new file mode 100644 index 0000000000..29b326faf0 --- /dev/null +++ b/examples/preact/column-resizing/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact()], +}) diff --git a/examples/preact/column-sizing/index.html b/examples/preact/column-sizing/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/column-sizing/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/column-sizing/package.json b/examples/preact/column-sizing/package.json new file mode 100644 index 0000000000..900d5303db --- /dev/null +++ b/examples/preact/column-sizing/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-preact-table-example-column-sizing", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/preact-table": "^9.0.0-alpha.45", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/preact/column-sizing/src/index.css b/examples/preact/column-sizing/src/index.css new file mode 100644 index 0000000000..d7f648b224 --- /dev/null +++ b/examples/preact/column-sizing/src/index.css @@ -0,0 +1,419 @@ +* { + box-sizing: border-box; +} + +html { + font-family: sans-serif; + font-size: 14px; +} + +table, +.divTable { + border: 1px solid lightgray; + width: fit-content; + border-collapse: collapse; + border-spacing: 0; +} + +.tr { + display: flex; +} + +tr, +.tr { + width: fit-content; + height: 30px; +} + +th, +.th, +td, +.td { + box-shadow: inset 0 0 0 1px lightgray; + padding: 0.25rem; +} + +th, +.th { + padding: 2px 4px; + position: relative; + font-weight: bold; + text-align: center; + height: 30px; +} + +td, +.td { + height: 30px; +} + +.resizer { + position: absolute; + top: 0; + height: 100%; + width: 5px; + background: rgba(0, 0, 0, 0.5); + cursor: col-resize; + user-select: none; + touch-action: none; +} + +.resizer.ltr { + right: 0; +} + +.resizer.rtl { + left: 0; +} + +.resizer.isResizing { + background: blue; + opacity: 1; +} + +@media (hover: hover) { + .resizer { + opacity: 0; + } + + *:hover > .resizer { + opacity: 1; + } +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/preact/column-sizing/src/main.tsx b/examples/preact/column-sizing/src/main.tsx new file mode 100644 index 0000000000..cae3109dcc --- /dev/null +++ b/examples/preact/column-sizing/src/main.tsx @@ -0,0 +1,278 @@ +import { useMemo, useReducer, useState } from 'preact/hooks' +import { render } from 'preact' +import { + columnSizingFeature, + createColumnHelper, + tableFeatures, + useTable, +} from '@tanstack/preact-table' +import { makeData } from './makeData' +import type { Person } from './makeData' +import './index.css' + +const _features = tableFeatures({ columnSizingFeature }) + +const columnHelper = createColumnHelper() + +// This is not the Column Resizing Example, this is a simplified version that just sets static column sizes +function App() { + const [data, setData] = useState(() => makeData(20)) + const refreshData = () => setData(makeData(20)) + const stressTest = () => setData(makeData(1_000)) + + const columns = useMemo( + () => + columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + size: 120, // initial size + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + size: 120, + }), + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + size: 100, + }), + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + size: 80, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + size: 200, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + size: 200, + }), + ]), + [], + ) + + const rerender = useReducer(() => ({}), {})[1] + + const table = useTable( + { + _features, + _rowModels: {}, + columns, + data, + debugTable: true, + debugHeaders: true, + debugColumns: true, + }, + (state) => state, // default selector + ) + + return ( +
+
+ + +
+
+
{'Initial Column Sizes'}
+
+ {table.getAllColumns().map((column) => ( +
+ +
+ ))} +
+
+
+
{''} +
+
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getAllCells().map((cell) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( + + )} +
+
+ +
+
+
+
{'
(relative)'}
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => ( +
+ {headerGroup.headers.map((header) => ( +
+ {header.isPlaceholder ? null : ( + + )} +
+
+ ))} +
+ ))} +
+
+ {table.getRowModel().rows.map((row) => ( +
+ {row.getAllCells().map((cell) => ( +
+ +
+ ))} +
+ ))} +
+
+
+
+
{'
(absolute positioning)'}
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => ( +
+ {headerGroup.headers.map((header) => ( +
+ {header.isPlaceholder ? null : ( + + )} +
+
+ ))} +
+ ))} +
+
+ {table.getRowModel().rows.map((row) => ( +
+ {row.getAllCells().map((cell) => ( +
+ +
+ ))} +
+ ))} +
+
+
+
+ +
{JSON.stringify(table.state, null, 2)}
+
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render(, rootElement) diff --git a/examples/preact/column-sizing/src/makeData.ts b/examples/preact/column-sizing/src/makeData.ts new file mode 100644 index 0000000000..c0f87b8b19 --- /dev/null +++ b/examples/preact/column-sizing/src/makeData.ts @@ -0,0 +1,46 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + status: string + progress: number +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + } + }) + } + + return makeDataLevel() +} diff --git a/examples/preact/column-sizing/src/vite-env.d.ts b/examples/preact/column-sizing/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/preact/column-sizing/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/preact/column-sizing/tsconfig.json b/examples/preact/column-sizing/tsconfig.json new file mode 100644 index 0000000000..c1e0bb7b6c --- /dev/null +++ b/examples/preact/column-sizing/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/column-sizing/vite.config.ts b/examples/preact/column-sizing/vite.config.ts new file mode 100644 index 0000000000..29b326faf0 --- /dev/null +++ b/examples/preact/column-sizing/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact()], +}) diff --git a/examples/preact/column-visibility/index.html b/examples/preact/column-visibility/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/column-visibility/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/column-visibility/package.json b/examples/preact/column-visibility/package.json new file mode 100644 index 0000000000..88c4c1510a --- /dev/null +++ b/examples/preact/column-visibility/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-preact-table-example-column-visibility", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/preact-table": "^9.0.0-alpha.45", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/preact/column-visibility/src/index.css b/examples/preact/column-visibility/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/preact/column-visibility/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/preact/column-visibility/src/main.tsx b/examples/preact/column-visibility/src/main.tsx new file mode 100644 index 0000000000..13bf268788 --- /dev/null +++ b/examples/preact/column-visibility/src/main.tsx @@ -0,0 +1,168 @@ +import { useReducer, useState } from 'preact/hooks' +import { render } from 'preact' +import { + columnVisibilityFeature, + createColumnHelper, + tableFeatures, + useTable, +} from '@tanstack/preact-table' +import { makeData } from './makeData' +import type { Person } from './makeData' +import './index.css' + +const _features = tableFeatures({ columnVisibilityFeature }) + +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.group({ + header: 'Name', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }), + ]), + }), + columnHelper.group({ + header: 'Info', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + }), + columnHelper.group({ + header: 'More Info', + columns: columnHelper.columns([ + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), + ]), + }), + ]), + }), +]) + +function App() { + const [data, setData] = useState(() => makeData(20)) + const refreshData = () => setData(makeData(20)) + const stressTest = () => setData(makeData(1_000)) + const rerender = useReducer(() => ({}), {})[1] + + const table = useTable( + { + _features, + _rowModels: {}, + columns, + data, + debugTable: true, + debugHeaders: true, + debugColumns: true, + }, + (state) => state, // default selector + ) + + return ( +
+
+ + +
+
+
+
+ +
+ {table.getAllLeafColumns().map((column) => { + return ( +
+ +
+ ) + })} +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + ))} + + ))} + + + {table.getFooterGroups().map((footerGroup) => ( + + {footerGroup.headers.map((header) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( + + )} +
+ +
+ {header.isPlaceholder ? null : ( + + )} +
+
+ +
+
{JSON.stringify(table.state, null, 2)}
+
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render(, rootElement) diff --git a/examples/preact/column-visibility/src/makeData.ts b/examples/preact/column-visibility/src/makeData.ts new file mode 100644 index 0000000000..c0f87b8b19 --- /dev/null +++ b/examples/preact/column-visibility/src/makeData.ts @@ -0,0 +1,46 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + status: string + progress: number +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + } + }) + } + + return makeDataLevel() +} diff --git a/examples/preact/column-visibility/src/vite-env.d.ts b/examples/preact/column-visibility/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/preact/column-visibility/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/preact/column-visibility/tsconfig.json b/examples/preact/column-visibility/tsconfig.json new file mode 100644 index 0000000000..c1e0bb7b6c --- /dev/null +++ b/examples/preact/column-visibility/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/column-visibility/vite.config.ts b/examples/preact/column-visibility/vite.config.ts new file mode 100644 index 0000000000..29b326faf0 --- /dev/null +++ b/examples/preact/column-visibility/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact()], +}) diff --git a/examples/preact/composable-tables/index.html b/examples/preact/composable-tables/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/composable-tables/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/composable-tables/package.json b/examples/preact/composable-tables/package.json new file mode 100644 index 0000000000..325f995345 --- /dev/null +++ b/examples/preact/composable-tables/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-preact-table-example-composable-tables", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/preact-store": "^0.13.1", + "@tanstack/preact-table": "^9.0.0-alpha.45", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/preact/composable-tables/src/components/cell-components.tsx b/examples/preact/composable-tables/src/components/cell-components.tsx new file mode 100644 index 0000000000..29c0c22ee9 --- /dev/null +++ b/examples/preact/composable-tables/src/components/cell-components.tsx @@ -0,0 +1,107 @@ +/** + * Cell-level components that use useCellContext + * + * These components can be used via the pre-bound cellComponents + * in AppCell children, e.g., + */ +import { useCellContext } from '../hooks/table' + +/** + * Generic text cell renderer + */ +export function TextCell() { + const cell = useCellContext() + return {cell.getValue()} +} + +/** + * Number cell with locale formatting + */ +export function NumberCell() { + const cell = useCellContext() + return {cell.getValue().toLocaleString()} +} + +/** + * Status badge cell for status column + */ +export function StatusCell() { + const cell = useCellContext<'relationship' | 'complicated' | 'single'>() + const status = cell.getValue() + return {status} +} + +/** + * Progress bar cell + */ +export function ProgressCell() { + const cell = useCellContext() + const progress = cell.getValue() + return ( +
+
+
+ ) +} + +/** + * Row actions cell - actions for the current row + */ +export function RowActionsCell() { + const cell = useCellContext() + const row = cell.row + + return ( +
+ + + +
+ ) +} + +/** + * Price cell with currency formatting + */ +export function PriceCell() { + const cell = useCellContext() + return ( + + $ + {cell.getValue().toLocaleString(undefined, { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })} + + ) +} + +/** + * Category badge cell + */ +export function CategoryCell() { + const cell = useCellContext<'electronics' | 'clothing' | 'food' | 'books'>() + const category = cell.getValue() + return {category} +} diff --git a/examples/preact/composable-tables/src/components/header-components.tsx b/examples/preact/composable-tables/src/components/header-components.tsx new file mode 100644 index 0000000000..d3e7309f90 --- /dev/null +++ b/examples/preact/composable-tables/src/components/header-components.tsx @@ -0,0 +1,72 @@ +/** + * Header-level components that use useHeaderContext + * + * These components can be used via the pre-bound headerComponents + * in AppHeader children, e.g., + */ +import { useHeaderContext } from '../hooks/table' + +/** + * Sort indicator showing current sort direction + */ +export function SortIndicator() { + const header = useHeaderContext() + const sorted = header.column.getIsSorted() + + if (!sorted) return null + + return ( + {sorted === 'asc' ? '🔼' : '🔽'} + ) +} + +/** + * Column filter input + */ +export function ColumnFilter() { + const header = useHeaderContext() + + if (!header.column.getCanFilter()) return null + + const columnFilterValue = header.column.getFilterValue() + + return ( +
e.stopPropagation()}> + + header.column.setFilterValue((e.target as HTMLInputElement).value) + } + placeholder={`Filter ${header.column.id}...`} + /> +
+ ) +} + +/** + * Footer showing the column ID + */ +export function FooterColumnId() { + const header = useHeaderContext() + return {header.column.id} +} + +/** + * Footer showing a summary/aggregation for numeric columns + */ +export function FooterSum() { + const header = useHeaderContext() + const table = header.getContext().table + const rows = table.getFilteredRowModel().rows + + // Calculate sum for numeric columns + const sum = rows.reduce((acc, row) => { + const value = row.getValue(header.column.id) + return acc + (typeof value === 'number' ? value : 0) + }, 0) + + return ( + {sum > 0 ? sum.toLocaleString() : '—'} + ) +} diff --git a/examples/preact/composable-tables/src/components/table-components.tsx b/examples/preact/composable-tables/src/components/table-components.tsx new file mode 100644 index 0000000000..33acb335d7 --- /dev/null +++ b/examples/preact/composable-tables/src/components/table-components.tsx @@ -0,0 +1,123 @@ +/** + * Table-level components that use useTableContext + * + * These components can be used via the pre-bound tableComponents + * directly on the table object, e.g., + */ +import { useTableContext } from '../hooks/table' +import type { PaginationState } from '@tanstack/preact-table' + +/** + * Pagination controls for the table + */ +export function PaginationControls() { + const table = useTableContext() + + return ( +
+ + + + + + Page{' '} + + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + + + + | Go to page: + { + const page = (e.target as HTMLInputElement).value + ? Number((e.target as HTMLInputElement).value) - 1 + : 0 + table.setPageIndex(page) + }} + /> + + +
+ ) +} + +/** + * Row count display + */ +export function RowCount() { + const table = useTableContext() + + return ( +
+ Showing {table.getRowModel().rows.length.toLocaleString()} of{' '} + {table.getRowCount().toLocaleString()} rows +
+ ) +} + +/** + * Table toolbar with title and actions + */ +export function TableToolbar({ + title, + onRefresh, + onStressTest, +}: { + title: string + onRefresh?: () => void + onStressTest?: () => void +}) { + const table = useTableContext() + + return ( +
+

{title}

+
+ {onRefresh && } + {onStressTest && ( + + )} + + +
+
+ ) +} diff --git a/examples/preact/composable-tables/src/hooks/table.ts b/examples/preact/composable-tables/src/hooks/table.ts new file mode 100644 index 0000000000..8c6fa188fb --- /dev/null +++ b/examples/preact/composable-tables/src/hooks/table.ts @@ -0,0 +1,105 @@ +/** + * Custom table hook setup using createTableHook + * + * This file creates a custom useAppTable hook with pre-bound components. + * Features, row models, and default options are defined once here and shared across all tables. + * Context hooks and a pre-bound createAppColumnHelper are also exported. + */ +import { + columnFilteringFeature, + createFilteredRowModel, + createPaginatedRowModel, + createSortedRowModel, + createTableHook, + filterFns, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/preact-table' + +// Import table-level components +import { + PaginationControls, + RowCount, + TableToolbar, +} from '../components/table-components' + +// Import cell-level components +import { + CategoryCell, + NumberCell, + PriceCell, + ProgressCell, + RowActionsCell, + StatusCell, + TextCell, +} from '../components/cell-components' + +// Import header/footer-level components (both use useHeaderContext) +import { + ColumnFilter, + FooterColumnId, + FooterSum, + SortIndicator, +} from '../components/header-components' + +/** + * Create the custom table hook with all pre-bound components. + * This exports: + * - createAppColumnHelper: Create column definitions with TFeatures already bound + * - useAppTable: Hook for creating tables with TFeatures baked in + * - useTableContext: Access table instance in tableComponents + * - useCellContext: Access cell instance in cellComponents + * - useHeaderContext: Access header instance in headerComponents + */ +export const { + createAppColumnHelper, + useAppTable, + useTableContext, + useCellContext, + useHeaderContext, +} = createTableHook({ + // Features are set once here and shared across all tables + _features: tableFeatures({ + columnFilteringFeature, + rowPaginationFeature, + rowSortingFeature, + }), + + // Row models are set once here + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + }, + + // set any default table options here too + getRowId: (row) => row.id, + + // Register table-level components (accessible via table.ComponentName) + tableComponents: { + PaginationControls, + RowCount, + TableToolbar, + }, + + // Register cell-level components (accessible via cell.ComponentName in AppCell) + cellComponents: { + TextCell, + NumberCell, + StatusCell, + ProgressCell, + RowActionsCell, + PriceCell, + CategoryCell, + }, + + // Register header/footer-level components (accessible via header.ComponentName in AppHeader/AppFooter) + headerComponents: { + SortIndicator, + ColumnFilter, + FooterColumnId, + FooterSum, + }, +}) diff --git a/examples/preact/composable-tables/src/index.css b/examples/preact/composable-tables/src/index.css new file mode 100644 index 0000000000..4d8fef0142 --- /dev/null +++ b/examples/preact/composable-tables/src/index.css @@ -0,0 +1,605 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + width: 100%; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th, +td { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 8px 12px; + text-align: left; +} + +th { + background-color: #f5f5f5; + font-weight: 600; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +.table-container { + padding: 16px; + max-width: 1200px; + margin: 0 auto; + border-collapse: collapse; + border-spacing: 0; +} + +.pagination { + display: flex; + align-items: center; + gap: 8px; + margin-top: 16px; + flex-wrap: wrap; +} + +.pagination button { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px 8px; + cursor: pointer; + background: white; +} + +.pagination button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.pagination input { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px; + width: 64px; +} + +.pagination select { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px; +} + +.sort-indicator { + margin-left: 4px; +} + +.column-filter { + margin-top: 4px; +} + +.column-filter input { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px; + width: 100%; + font-size: 12px; +} + +.sortable-header { + cursor: pointer; + user-select: none; +} + +.sortable-header:hover { + background-color: #e8e8e8; +} + +.row-actions { + display: flex; + gap: 4px; +} + +.row-actions button { + border: 1px solid #ccc; + border-radius: 4px; + padding: 2px 8px; + cursor: pointer; + background: white; + font-size: 12px; +} + +.row-actions button:hover { + background-color: #f0f0f0; +} + +.status-badge { + display: inline-block; + padding: 2px 8px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; +} + +.status-badge.relationship { + background-color: #d4edda; + color: #155724; +} + +.status-badge.complicated { + background-color: #fff3cd; + color: #856404; +} + +.status-badge.single { + background-color: #cce5ff; + color: #004085; +} + +.progress-bar { + width: 100%; + height: 8px; + background-color: #e9ecef; + border-radius: 4px; + overflow: hidden; +} + +.progress-bar-fill { + height: 100%; + background-color: #007bff; + transition: width 0.2s; +} + +.table-toolbar { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + flex-wrap: wrap; + gap: 8px; + border-collapse: collapse; + border-spacing: 0; +} + +.table-toolbar h2 { + margin: 0; + border-collapse: collapse; + border-spacing: 0; +} + +.table-toolbar-actions { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.table-toolbar button { + border: 1px solid #ccc; + border-radius: 4px; + padding: 8px 16px; + cursor: pointer; + background: white; + border-collapse: collapse; + border-spacing: 0; +} + +.table-toolbar button:hover { + background-color: #f0f0f0; + border-collapse: collapse; + border-spacing: 0; +} + +.row-count { + color: #666; + font-size: 14px; + margin-top: 8px; +} + +.app { + padding: 16px; +} + +.app h1 { + text-align: center; + margin-bottom: 8px; +} + +.description { + text-align: center; + color: #666; + margin-bottom: 32px; +} + +.description code { + background-color: #f5f5f5; + padding: 2px 6px; + border-radius: 4px; + font-size: 13px; +} + +.table-divider { + height: 48px; + border-bottom: 1px solid #e0e0e0; + margin: 32px auto; + max-width: 1200px; + border-collapse: collapse; + border-spacing: 0; +} + +.category-badge { + display: inline-block; + padding: 2px 8px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; + text-transform: capitalize; +} + +.category-badge.electronics { + background-color: #e3f2fd; + color: #1565c0; +} + +.category-badge.clothing { + background-color: #fce4ec; + color: #c2185b; +} + +.category-badge.food { + background-color: #e8f5e9; + color: #2e7d32; +} + +.category-badge.books { + background-color: #fff8e1; + color: #f57c00; +} + +.price { + font-weight: 600; + color: #2e7d32; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/preact/composable-tables/src/main.tsx b/examples/preact/composable-tables/src/main.tsx new file mode 100644 index 0000000000..473ca140e7 --- /dev/null +++ b/examples/preact/composable-tables/src/main.tsx @@ -0,0 +1,435 @@ +import { useCallback, useMemo, useState } from 'preact/hooks' +import { render } from 'preact' +import { createAppColumnHelper, useAppTable } from './hooks/table' +import { makeData, makeProductData } from './makeData' +import type { Person, Product } from './makeData' +import './index.css' +// Import cell components directly - they use useCellContext internally + +// Create column helpers with TFeatures already bound - only need TData! +const personColumnHelper = createAppColumnHelper() +const productColumnHelper = createAppColumnHelper() + +// Users Table Component - Original implementation +function UsersTable() { + // Data state + const [data, setData] = useState(() => makeData(1000)) + + // Refresh data callback + const refreshData = useCallback(() => { + setData(makeData(1_000)) + }, []) + + const stressTest = useCallback(() => { + setData(makeData(200_000)) + }, []) + + // Define columns using the column helper + const columns = useMemo( + () => + // NOTE: You must use `createAppColumnHelper` instead of `createColumnHelper` when using pre-bound components like + personColumnHelper.columns([ + personColumnHelper.accessor('firstName', { + header: 'First Name', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + personColumnHelper.accessor('lastName', { + header: 'Last Name', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + personColumnHelper.accessor('age', { + header: 'Age', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + personColumnHelper.accessor('visits', { + header: 'Visits', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + personColumnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + personColumnHelper.accessor('progress', { + header: 'Progress', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + personColumnHelper.display({ + id: 'actions', + header: 'Actions', + cell: ({ cell }) => , + }), + ]), + [], + ) + + // Create the table - _features and _rowModels are already configured! + const table = useAppTable( + { + columns, + data, + debugTable: true, + // more table options + }, + (state) => state, // default selector + ) + + return ( + // Main selector on AppTable - selects all needed state in one place + ({ + // subscribe to specific states for re-rendering if you are optimizing for maximum performance + pagination: state.pagination, + sorting: state.sorting, + columnFilters: state.columnFilters, + })} + > + {({ sorting, columnFilters }) => ( +
+ {/* Table toolbar using pre-bound component */} + + + {/* Table element */} + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((h) => ( + + {(header) => ( + + )} + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getAllCells().map((c) => ( + + {(cell) => ( + + )} + + ))} + + ))} + + + {table.getFooterGroups().map((footerGroup) => ( + + {footerGroup.headers.map((f) => ( + + {(footer) => { + const columnId = footer.column.id + const hasFilter = columnFilters.some( + (cf) => cf.id === columnId, + ) + + return ( + + ) + }} + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( + <> + + + + {/* Show sort order number when multiple columns sorted */} + {sorting.length > 1 && + sorting.findIndex( + (s) => s.id === header.column.id, + ) > -1 && ( + + {sorting.findIndex( + (s) => s.id === header.column.id, + ) + 1} + + )} + + )} +
+ {/* Cell components are pre-bound via AppCell */} + +
+ {footer.isPlaceholder ? null : ( + <> + {/* Use FooterSum for numeric columns, FooterColumnId for others */} + {columnId === 'age' || + columnId === 'visits' || + columnId === 'progress' ? ( + <> + + {hasFilter && ( + + {' '} + (filtered) + + )} + + ) : columnId === 'actions' ? null : ( + <> + + {hasFilter && ( + + {' '} + ✓ + + )} + + )} + + )} +
+ + {/* Pagination using pre-bound component */} + + + {/* Row count using pre-bound component */} + +
+ )} +
+ ) +} + +// Products Table Component - New implementation using same hook and components +function ProductsTable() { + // Data state + const [data, setData] = useState(() => makeProductData(500)) + + // Refresh data callback + const refreshData = useCallback(() => { + setData(makeProductData(500)) + }, []) + + const stressTest = useCallback(() => { + setData(makeProductData(200_000)) + }, []) + + // Define columns using the column helper - different structure than Users table + const columns = useMemo( + () => + productColumnHelper.columns([ + productColumnHelper.accessor('name', { + header: 'Product Name', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + productColumnHelper.accessor('category', { + header: 'Category', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + productColumnHelper.accessor('price', { + header: 'Price', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + productColumnHelper.accessor('stock', { + header: 'In Stock', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + productColumnHelper.accessor('rating', { + header: 'Rating', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + ]), + [], + ) + + // Create the table using the same useAppTable hook + const table = useAppTable( + { + debugTable: true, + columns, + data, + getRowId: (row) => row.id, + }, + (state) => state, // default selector + ) + + return ( + ({ + pagination: state.pagination, + sorting: state.sorting, + columnFilters: state.columnFilters, + })} + > + {({ sorting, columnFilters }) => ( +
+ {/* Table toolbar using the same pre-bound component */} + + + {/* Table element */} + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((h) => ( + + {(header) => ( + + )} + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getAllCells().map((c) => ( + + {(cell) => ( + + )} + + ))} + + ))} + + + {table.getFooterGroups().map((footerGroup) => ( + + {footerGroup.headers.map((f) => ( + + {(footer) => { + const columnId = footer.column.id + const hasFilter = columnFilters.some( + (cf) => cf.id === columnId, + ) + + return ( + + ) + }} + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( + <> + + + + {sorting.length > 1 && + sorting.findIndex( + (s) => s.id === header.column.id, + ) > -1 && ( + + {sorting.findIndex( + (s) => s.id === header.column.id, + ) + 1} + + )} + + )} +
+ {/* Cell components are pre-bound via AppCell */} + +
+ {footer.isPlaceholder ? null : ( + <> + {/* Use FooterSum for numeric columns, FooterColumnId for others */} + {columnId === 'price' || + columnId === 'stock' || + columnId === 'rating' ? ( + <> + + {hasFilter && ( + + {' '} + (filtered) + + )} + + ) : ( + <> + + {hasFilter && ( + + {' '} + ✓ + + )} + + )} + + )} +
+ + {/* Pagination using the same pre-bound component */} + + + {/* Row count using the same pre-bound component */} + +
+ )} +
+ ) +} + +function App() { + return ( +
+

Composable Tables Example

+

+ Both tables below use the same useAppTable hook and + shareable components, but with different data types and column + configurations. +

+ + {/* Original Users Table */} + + +
+ + {/* New Products Table */} + +
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render(, rootElement) diff --git a/examples/preact/composable-tables/src/makeData.ts b/examples/preact/composable-tables/src/makeData.ts new file mode 100644 index 0000000000..17dec1c6de --- /dev/null +++ b/examples/preact/composable-tables/src/makeData.ts @@ -0,0 +1,77 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +export type Product = { + id: string + name: string + category: 'electronics' | 'clothing' | 'food' | 'books' + price: number + stock: number + rating: number +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +const newProduct = (): Product => { + return { + id: faker.string.uuid(), + name: faker.commerce.productName(), + category: faker.helpers.shuffle([ + 'electronics', + 'clothing', + 'food', + 'books', + ])[0], + price: parseFloat(faker.commerce.price({ min: 5, max: 500 })), + stock: faker.number.int({ min: 0, max: 200 }), + rating: faker.number.int({ min: 0, max: 100 }), + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} + +export function makeProductData(count: number): Array { + return range(count).map(() => newProduct()) +} diff --git a/examples/preact/composable-tables/src/vite-env.d.ts b/examples/preact/composable-tables/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/preact/composable-tables/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/preact/composable-tables/tsconfig.json b/examples/preact/composable-tables/tsconfig.json new file mode 100644 index 0000000000..c1e0bb7b6c --- /dev/null +++ b/examples/preact/composable-tables/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/composable-tables/vite.config.ts b/examples/preact/composable-tables/vite.config.ts new file mode 100644 index 0000000000..29b326faf0 --- /dev/null +++ b/examples/preact/composable-tables/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact()], +}) diff --git a/examples/preact/custom-plugin/index.html b/examples/preact/custom-plugin/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/custom-plugin/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/custom-plugin/package.json b/examples/preact/custom-plugin/package.json new file mode 100644 index 0000000000..efaa3325de --- /dev/null +++ b/examples/preact/custom-plugin/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-preact-table-example-custom-plugin", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/preact-table": "^9.0.0-alpha.45", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/preact/custom-plugin/src/index.css b/examples/preact/custom-plugin/src/index.css new file mode 100644 index 0000000000..f8ffbb8e90 --- /dev/null +++ b/examples/preact/custom-plugin/src/index.css @@ -0,0 +1,373 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +tr { + border-bottom: 1px solid lightgray; +} + +button:disabled { + opacity: 0.5; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/preact/custom-plugin/src/main.tsx b/examples/preact/custom-plugin/src/main.tsx new file mode 100644 index 0000000000..d8d1119060 --- /dev/null +++ b/examples/preact/custom-plugin/src/main.tsx @@ -0,0 +1,387 @@ +import { useMemo, useState } from 'preact/hooks' +import { render } from 'preact' +import './index.css' +import { + columnFilteringFeature, + createColumnHelper, + createFilteredRowModel, + createPaginatedRowModel, + createSortedRowModel, + filterFns, + functionalUpdate, + makeStateUpdater, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, + useTable, +} from '@tanstack/preact-table' +import { makeData } from './makeData' +import type { + Column, + OnChangeFn, + PreactTable, + TableFeature, + Updater, +} from '@tanstack/preact-table' +import type { Person } from './makeData' + +// TypeScript setup for our new feature with all of the same type-safety as stock TanStack Table features + +// define types for our new feature's custom state +export type DensityState = 'sm' | 'md' | 'lg' +export interface TableState_Density { + density: DensityState +} + +// define types for our new feature's table options +export interface TableOptions_Density { + enableDensity?: boolean + onDensityChange?: OnChangeFn +} + +// Define types for our new feature's table APIs +export interface Table_Density { + setDensity: (updater: Updater) => void + toggleDensity: (value?: DensityState) => void +} + +interface DensityPluginConstructors { + Table: Table_Density + TableOptions: TableOptions_Density + TableState: TableState_Density +} + +// Here is all of the actual javascript code for our new feature +export const densityPlugin: TableFeature = { + // define the new feature's initial state + getInitialState: (initialState) => { + return { + density: 'md', + ...initialState, // must come last + } + }, + + // define the new feature's default options + getDefaultTableOptions: (table) => { + return { + enableDensity: true, + onDensityChange: makeStateUpdater('density', table), + } + }, + // if you need to add a default column definition... + // getDefaultColumnDef: () => {}, + + // define the new feature's table instance methods + constructTableAPIs: (table) => { + table.setDensity = (updater) => { + const safeUpdater: Updater = (old) => { + const newState = functionalUpdate(updater, old) + return newState + } + return table.options.onDensityChange?.(safeUpdater) + } + table.toggleDensity = (value) => { + table.setDensity?.((old) => { + if (value) return value + return old === 'lg' ? 'md' : old === 'md' ? 'sm' : 'lg' // cycle through the 3 options + }) + } + }, + + // if you need to add row instance APIs... + // constructRowAPIs: (row) => {}, + + // if you need to add cell instance APIs... + // constructCellAPIs: (cell) => {}, + + // if you need to add column instance APIs... + // constructColumnAPIs: (column) => {}, + + // if you need to add header instance APIs... + // constructHeaderAPIs: (header) => {}, +} +// end of custom feature code + +// app code +const _features = tableFeatures({ + columnFilteringFeature, + rowSortingFeature, + rowPaginationFeature, + densityPlugin, // pass in our plugin just like any other stock feature +}) + +const columnHelper = createColumnHelper() + +function App() { + const columns = useMemo( + () => + columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }), + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + }), + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), + ]), + [], + ) + + const [data, setData] = useState(() => makeData(1_000)) + const refreshData = () => setData(makeData(1_000)) + const stressTest = () => setData(makeData(200_000)) + const [density, setDensity] = useState('md') + + const table = useTable( + { + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, + columns, + data, + debugTable: true, + state: { + density, // passing the density state to the table, TS is still happy :) + }, + onDensityChange: setDensity, // using the new onDensityChange option, TS is still happy :) + }, + (state) => state, // default selector + ) + + return ( +
+
+ + +
+
+ + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ) + })} + + ))} + + + {table.getRowModel().rows.map((row) => { + return ( + + {row.getAllCells().map((cell) => { + return ( + + ) + })} + + ) + })} + +
+
+ + {{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null} +
+ {header.column.getCanFilter() ? ( +
+ +
+ ) : null} +
+ +
+
+
+ + + + + +
Page
+ + {(table.store.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = (e.target as HTMLInputElement).value + ? Number((e.target as HTMLInputElement).value) - 1 + : 0 + table.setPageIndex(page) + }} + className="page-size-input" + /> + + +
+
+ Showing {table.getRowModel().rows.length.toLocaleString()} of{' '} + {table.getRowCount().toLocaleString()} Rows +
+
{JSON.stringify(table.state, null, 2)}
+
+ ) +} + +function Filter({ + column, + table, +}: { + column: Column + table: PreactTable +}) { + const firstValue = table + .getPreFilteredRowModel() + .flatRows[0]?.getValue(column.id) + + const columnFilterValue = column.getFilterValue() + + return typeof firstValue === 'number' ? ( +
+ + column.setFilterValue((old: [number, number]) => [ + (e.target as HTMLInputElement).value, + old[1], + ]) + } + placeholder={`Min`} + className="filter-input" + /> + + column.setFilterValue((old: [number, number]) => [ + old[0], + (e.target as HTMLInputElement).value, + ]) + } + placeholder={`Max`} + className="filter-input" + /> +
+ ) : ( + + column.setFilterValue((e.target as HTMLInputElement).value) + } + placeholder={`Search...`} + className="filter-select" + /> + ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render(, rootElement) diff --git a/examples/preact/custom-plugin/src/makeData.ts b/examples/preact/custom-plugin/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/preact/custom-plugin/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/preact/custom-plugin/src/vite-env.d.ts b/examples/preact/custom-plugin/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/preact/custom-plugin/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/preact/custom-plugin/tsconfig.json b/examples/preact/custom-plugin/tsconfig.json new file mode 100644 index 0000000000..c1e0bb7b6c --- /dev/null +++ b/examples/preact/custom-plugin/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/custom-plugin/vite.config.ts b/examples/preact/custom-plugin/vite.config.ts new file mode 100644 index 0000000000..29b326faf0 --- /dev/null +++ b/examples/preact/custom-plugin/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact()], +}) diff --git a/examples/preact/expanding/index.html b/examples/preact/expanding/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/expanding/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/expanding/package.json b/examples/preact/expanding/package.json new file mode 100644 index 0000000000..26a1446af3 --- /dev/null +++ b/examples/preact/expanding/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-preact-table-example-expanding", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/preact-table": "^9.0.0-alpha.45", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/preact/expanding/src/index.css b/examples/preact/expanding/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/preact/expanding/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/preact/expanding/src/main.tsx b/examples/preact/expanding/src/main.tsx new file mode 100644 index 0000000000..a89afd0872 --- /dev/null +++ b/examples/preact/expanding/src/main.tsx @@ -0,0 +1,345 @@ +import { useEffect, useMemo, useReducer, useRef, useState } from 'preact/hooks' +import { render } from 'preact' +import { + columnFilteringFeature, + createColumnHelper, + createExpandedRowModel, + createFilteredRowModel, + createPaginatedRowModel, + createSortedRowModel, + filterFns, + rowExpandingFeature, + rowPaginationFeature, + rowSelectionFeature, + rowSortingFeature, + sortFns, + tableFeatures, + useTable, +} from '@tanstack/preact-table' +import { makeData } from './makeData' +import type { JSX } from 'preact' +import type { Person } from './makeData' +import type { Column, Table } from '@tanstack/preact-table' +import './index.css' + +const _features = tableFeatures({ + columnFilteringFeature, + rowExpandingFeature, + rowPaginationFeature, + rowSortingFeature, + rowSelectionFeature, +}) + +const columnHelper = createColumnHelper() + +function App() { + const rerender = useReducer(() => ({}), {})[1] + + const columns = useMemo( + () => + columnHelper.columns([ + columnHelper.accessor('firstName', { + header: ({ table }) => ( + <> + {' '} + {' '} + First Name + + ), + cell: ({ row, getValue }) => ( +
+
+ {' '} + {row.getCanExpand() ? ( + + ) : ( + '🔵' + )}{' '} + {getValue()} +
+
+ ), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }), + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + filterFn: 'between', + }), + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), + ]), + [], + ) + + const [data, setData] = useState(() => makeData(100, 5, 3)) + const refreshData = () => setData(() => makeData(100, 5, 3)) + const stressTest = () => setData(() => makeData(10_000, 5, 3)) + + const table = useTable( + { + _features, + _rowModels: { + expandedRowModel: createExpandedRowModel(), + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, + columns, + data, + getSubRows: (row) => row.subRows, + // filterFromLeafRows: true, + // maxLeafRowFilterDepth: 0, + debugTable: true, + }, + (state) => state, // default selector + ) + + return ( +
+
+ + +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ) + })} + + ))} + + + {table.getRowModel().rows.map((row) => { + return ( + + {row.getAllCells().map((cell) => { + return ( + + ) + })} + + ) + })} + +
+ {header.isPlaceholder ? null : ( +
+ + {header.column.getCanFilter() ? ( +
+ +
+ ) : null} +
+ )} +
+ +
+
+
+ + + + + +
Page
+ + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = (e.target as HTMLInputElement).value + ? Number((e.target as HTMLInputElement).value) - 1 + : 0 + table.setPageIndex(page) + }} + className="page-size-input" + /> + + +
+
{table.getRowModel().rows.length.toLocaleString()} Rows
+
+ +
+
{JSON.stringify(table.state, null, 2)}
+
+ ) +} + +function Filter({ + column, + table, +}: { + column: Column + table: Table +}) { + const firstValue = table + .getPreFilteredRowModel() + .flatRows[0]?.getValue(column.id) + + const columnFilterValue = column.getFilterValue() + + return typeof firstValue === 'number' ? ( +
+ + column.setFilterValue((old: [number, number] | undefined) => [ + (e.target as HTMLInputElement).value, + old?.[1], + ]) + } + placeholder={`Min`} + className="filter-input" + /> + + column.setFilterValue((old: [number, number] | undefined) => [ + old?.[0], + (e.target as HTMLInputElement).value, + ]) + } + placeholder={`Max`} + className="filter-input" + /> +
+ ) : ( + + column.setFilterValue((e.target as HTMLInputElement).value) + } + placeholder={`Search...`} + className="filter-select" + /> + ) +} + +function IndeterminateCheckbox({ + indeterminate, + className = '', + checked, + onChange, + ...rest +}: { + indeterminate?: boolean + checked?: boolean + onChange?: (event: Event) => void +} & Omit, 'checked' | 'onChange'>) { + const ref = useRef(null) + + useEffect(() => { + if (typeof indeterminate === 'boolean' && ref.current) { + ref.current.indeterminate = !checked && indeterminate + } + }, [ref, indeterminate, checked]) + + return ( + + ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render(, rootElement) diff --git a/examples/preact/expanding/src/makeData.ts b/examples/preact/expanding/src/makeData.ts new file mode 100644 index 0000000000..95302cdf16 --- /dev/null +++ b/examples/preact/expanding/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/preact/expanding/src/vite-env.d.ts b/examples/preact/expanding/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/preact/expanding/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/preact/expanding/tsconfig.json b/examples/preact/expanding/tsconfig.json new file mode 100644 index 0000000000..c1e0bb7b6c --- /dev/null +++ b/examples/preact/expanding/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/expanding/vite.config.ts b/examples/preact/expanding/vite.config.ts new file mode 100644 index 0000000000..29b326faf0 --- /dev/null +++ b/examples/preact/expanding/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact()], +}) diff --git a/examples/preact/filters-faceted/index.html b/examples/preact/filters-faceted/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/filters-faceted/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/filters-faceted/package.json b/examples/preact/filters-faceted/package.json new file mode 100644 index 0000000000..c20da27b75 --- /dev/null +++ b/examples/preact/filters-faceted/package.json @@ -0,0 +1,25 @@ +{ + "name": "tanstack-preact-table-example-filters-faceted", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/match-sorter-utils": "^9.0.0-alpha.4", + "@tanstack/preact-pacer": "^0.22.0", + "@tanstack/preact-table": "^9.0.0-alpha.45", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/preact/filters-faceted/src/index.css b/examples/preact/filters-faceted/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/preact/filters-faceted/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/preact/filters-faceted/src/main.tsx b/examples/preact/filters-faceted/src/main.tsx new file mode 100644 index 0000000000..7357bf78a3 --- /dev/null +++ b/examples/preact/filters-faceted/src/main.tsx @@ -0,0 +1,358 @@ +import { render } from 'preact' +import { useEffect, useMemo, useReducer, useState } from 'preact/hooks' +import './index.css' +import { + columnFacetingFeature, + columnFilteringFeature, + createColumnHelper, + createFacetedMinMaxValues, + createFacetedRowModel, + createFacetedUniqueValues, + createFilteredRowModel, + createPaginatedRowModel, + filterFns, + rowPaginationFeature, + tableFeatures, + useTable, +} from '@tanstack/preact-table' +import { useDebouncedCallback } from '@tanstack/preact-pacer/debouncer' +import { makeData } from './makeData' +import type { JSX } from 'preact' +import type { + CellData, + Column, + RowData, + TableFeatures, +} from '@tanstack/preact-table' +import type { Person } from './makeData' + +const _features = tableFeatures({ + columnFacetingFeature, + columnFilteringFeature, + rowPaginationFeature, +}) + +const columnHelper = createColumnHelper() + +declare module '@tanstack/preact-table' { + // allows us to define custom properties for our columns + interface ColumnMeta< + TFeatures extends TableFeatures, + TData extends RowData, + TValue extends CellData = CellData, + > { + filterVariant?: 'text' | 'range' | 'select' + } +} + +function App() { + const columns = useMemo( + () => + columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + }), + columnHelper.accessor('age', { + header: () => 'Age', + meta: { + filterVariant: 'range', + }, + }), + columnHelper.accessor('visits', { + header: () => Visits, + meta: { + filterVariant: 'range', + }, + }), + columnHelper.accessor('status', { + header: 'Status', + meta: { + filterVariant: 'select', + }, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + meta: { + filterVariant: 'range', + }, + }), + ]), + [], + ) + + const [data, setData] = useState>(() => makeData(5_000)) + const refreshData = () => setData((_old) => makeData(5_000)) + const stressTest = () => setData((_old) => makeData(200_000)) + const rerender = useReducer(() => ({}), {})[1] + + const table = useTable( + { + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), // client-side filtering + paginatedRowModel: createPaginatedRowModel(), + facetedRowModel: createFacetedRowModel(), // client-side faceting + facetedMinMaxValues: createFacetedMinMaxValues(), // generate min/max values for range filter + facetedUniqueValues: createFacetedUniqueValues(), // generate unique values for select filter/autocomplete + }, + columns, + data, + debugTable: true, + debugHeaders: true, + debugColumns: false, + }, + (state) => state, // default selector + ) + + return ( +
+
+ + +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ) + })} + + ))} + + + {table.getRowModel().rows.map((row) => { + return ( + + {row.getAllCells().map((cell) => { + return ( + + ) + })} + + ) + })} + +
+ {header.isPlaceholder ? null : ( + <> +
+ +
+ {header.column.getCanFilter() ? ( +
+ +
+ ) : null} + + )} +
+ +
+
+
+ + + + + +
Page
+ + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = (e.target as HTMLInputElement).value + ? Number((e.target as HTMLInputElement).value) - 1 + : 0 + table.setPageIndex(page) + }} + className="page-size-input" + /> + + +
+
+ {table.getPrePaginatedRowModel().rows.length.toLocaleString()} Rows +
+
+ +
+
{JSON.stringify(table.state, null, 2)}
+
+ ) +} + +function Filter({ column }: { column: Column }) { + const { filterVariant } = column.columnDef.meta ?? {} + + const columnFilterValue = column.getFilterValue() + + const minMaxValues = column.getFacetedMinMaxValues() + + const sortedUniqueValues = useMemo( + () => + filterVariant === 'range' + ? [] + : Array.from(column.getFacetedUniqueValues().keys()) + .sort() + .slice(0, 5000), + [column.getFacetedUniqueValues(), filterVariant], + ) + + return filterVariant === 'range' ? ( +
+
+ + column.setFilterValue((old: [number, number] | undefined) => [ + value, + old?.[1], + ]) + } + placeholder={`Min ${ + minMaxValues?.[0] !== undefined ? `(${minMaxValues[0]})` : '' + }`} + className="filter-input" + /> + + column.setFilterValue((old: [number, number] | undefined) => [ + old?.[0], + value, + ]) + } + placeholder={`Max ${minMaxValues?.[1] ? `(${minMaxValues[1]})` : ''}`} + className="filter-input" + /> +
+
+
+ ) : filterVariant === 'select' ? ( + + ) : ( + <> + {/* Autocomplete suggestions from faceted values feature */} + + {sortedUniqueValues.map((value: any) => ( + + column.setFilterValue(value)} + placeholder={`Search... (${column.getFacetedUniqueValues().size})`} + className="filter-select" + list={column.id + 'list'} + /> +
+ + ) +} + +// A typical debounced input preact component +function DebouncedInput({ + value: initialValue, + onChange, + debounce = 500, + ...props +}: { + value: string | number + onChange: (value: string | number) => void + debounce?: number +} & Record) { + const [value, setValue] = useState(initialValue) + + useEffect(() => { + setValue(initialValue) + }, [initialValue]) + + const debouncedOnChange = useDebouncedCallback(onChange, { wait: debounce }) + + return ( + { + const nextValue = (e.target as HTMLInputElement).value + setValue(nextValue) + debouncedOnChange(nextValue) + }} + /> + ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render(, rootElement) diff --git a/examples/preact/filters-faceted/src/makeData.ts b/examples/preact/filters-faceted/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/preact/filters-faceted/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/preact/filters-faceted/src/vite-env.d.ts b/examples/preact/filters-faceted/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/preact/filters-faceted/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/preact/filters-faceted/tsconfig.json b/examples/preact/filters-faceted/tsconfig.json new file mode 100644 index 0000000000..c1e0bb7b6c --- /dev/null +++ b/examples/preact/filters-faceted/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/filters-faceted/vite.config.ts b/examples/preact/filters-faceted/vite.config.ts new file mode 100644 index 0000000000..29b326faf0 --- /dev/null +++ b/examples/preact/filters-faceted/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact()], +}) diff --git a/examples/preact/filters-fuzzy/index.html b/examples/preact/filters-fuzzy/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/filters-fuzzy/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/filters-fuzzy/package.json b/examples/preact/filters-fuzzy/package.json new file mode 100644 index 0000000000..3a2f9b4051 --- /dev/null +++ b/examples/preact/filters-fuzzy/package.json @@ -0,0 +1,25 @@ +{ + "name": "tanstack-preact-table-example-filters-fuzzy", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/match-sorter-utils": "^9.0.0-alpha.4", + "@tanstack/preact-pacer": "^0.22.0", + "@tanstack/preact-table": "^9.0.0-alpha.45", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/preact/filters-fuzzy/src/index.css b/examples/preact/filters-fuzzy/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/preact/filters-fuzzy/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/preact/filters-fuzzy/src/main.tsx b/examples/preact/filters-fuzzy/src/main.tsx new file mode 100644 index 0000000000..244b28a30a --- /dev/null +++ b/examples/preact/filters-fuzzy/src/main.tsx @@ -0,0 +1,337 @@ +import { render } from 'preact' +import { useEffect, useMemo, useReducer, useState } from 'preact/hooks' +import './index.css' +import { + columnFilteringFeature, + createColumnHelper, + createFilteredRowModel, + createPaginatedRowModel, + createSortedRowModel, + filterFns, + globalFilteringFeature, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, + useTable, +} from '@tanstack/preact-table' +import { useDebouncedCallback } from '@tanstack/preact-pacer/debouncer' +import { compareItems, rankItem } from '@tanstack/match-sorter-utils' +import { makeData } from './makeData' +import type { JSX } from 'preact' +import type { Person } from './makeData' +import type { Column, FilterFn, SortFn } from '@tanstack/preact-table' + +// A TanStack fork of Kent C. Dodds' match-sorter library that provides ranking information +import type { RankingInfo } from '@tanstack/match-sorter-utils' + +const _features = tableFeatures({ + columnFilteringFeature, + globalFilteringFeature, + rowSortingFeature, + rowPaginationFeature, +}) + +const columnHelper = createColumnHelper() + +// Define a custom fuzzy filter function that will apply ranking info to rows (using match-sorter utils) +const fuzzyFilter: FilterFn = ( + row, + columnId, + value, + addMeta, +) => { + // Rank the item + const itemRank = rankItem(row.getValue(columnId), value) + + // Store the itemRank info + addMeta?.({ + itemRank, + }) + + // Return if the item should be filtered in/out + return itemRank.passed +} + +// Define a custom fuzzy sort function that will sort by rank if the row has ranking information +const fuzzySort: SortFn = (rowA, rowB, columnId) => { + let dir = 0 + + // Only sort by rank if the column has ranking information + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (rowA.columnFiltersMeta[columnId]) { + dir = compareItems( + rowA.columnFiltersMeta[columnId].itemRank!, + rowB.columnFiltersMeta[columnId].itemRank!, + ) + } + + // Provide an alphanumeric fallback for when the item ranks are equal + return dir === 0 ? sortFns.alphanumeric(rowA, rowB, columnId) : dir +} + +declare module '@tanstack/preact-table' { + // add fuzzy filter to the filterFns + interface FilterFns { + fuzzy: FilterFn + } + interface FilterMeta { + itemRank?: RankingInfo + } +} + +function App() { + const rerender = useReducer(() => ({}), {})[1] + + const columns = useMemo( + () => + columnHelper.columns([ + columnHelper.accessor('id', { + filterFn: 'equalsString', // note: normal non-fuzzy filter column - exact match required + }), + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + filterFn: 'includesStringSensitive', // note: normal non-fuzzy filter column - case sensitive + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + filterFn: 'includesString', // note: normal non-fuzzy filter column - case insensitive + }), + columnHelper.accessor((row) => `${row.firstName} ${row.lastName}`, { + id: 'fullName', + header: 'Full Name', + cell: (info) => info.getValue(), + filterFn: 'fuzzy', // using our custom fuzzy filter function + // filterFn: fuzzyFilter, //or just define with the function + sortFn: fuzzySort, // sort by fuzzy rank (falls back to alphanumeric) + }), + ]), + [], + ) + + const [data, setData] = useState>(() => makeData(5_000)) + const refreshData = () => setData((_old) => makeData(5_000)) + const stressTest = () => setData((_old) => makeData(200_000)) + + const table = useTable( + { + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel({ + ...filterFns, + fuzzy: fuzzyFilter, + }), + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, + columns, + data, + globalFilterFn: 'fuzzy', // apply fuzzy filter to the global filter (most common use case for fuzzy filter) + debugTable: true, + debugHeaders: true, + debugColumns: false, + }, + (state) => state, // default selector + ) + + // apply the fuzzy sort if the fullName column is being filtered + useEffect(() => { + if (table.store.state.columnFilters[0]?.id === 'fullName') { + if (table.store.state.sorting[0]?.id !== 'fullName') { + table.setSorting([{ id: 'fullName', desc: false }]) + } + } + }, [table.store.state.columnFilters[0]?.id]) + + return ( +
+
+ + +
+
+ table.setGlobalFilter(String(value))} + className="summary-panel" + placeholder="Search all columns..." + /> +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ) + })} + + ))} + + + {table.getRowModel().rows.map((row) => { + return ( + + {row.getAllCells().map((cell) => { + return ( + + ) + })} + + ) + })} + +
+ {header.isPlaceholder ? null : ( + <> +
+ + {{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null} +
+ {header.column.getCanFilter() ? ( +
+ +
+ ) : null} + + )} +
+ +
+
+
+ + + + + +
Page
+ + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = (e.target as HTMLInputElement).value + ? Number((e.target as HTMLInputElement).value) - 1 + : 0 + table.setPageIndex(page) + }} + className="page-size-input" + /> + + +
+
+ {table.getPrePaginatedRowModel().rows.length.toLocaleString()} Rows +
+
+ +
+
{JSON.stringify(table.state, null, 2)}
+
+ ) +} + +function Filter({ column }: { column: Column }) { + const columnFilterValue = column.getFilterValue() + + return ( + column.setFilterValue(value)} + placeholder={`Search...`} + className="filter-select" + /> + ) +} + +// A typical debounced input preact component +function DebouncedInput({ + value: initialValue, + onChange, + debounce = 500, + ...props +}: { + value: string | number + onChange: (value: string | number) => void + debounce?: number +} & Record) { + const [value, setValue] = useState(initialValue) + + useEffect(() => { + setValue(initialValue) + }, [initialValue]) + + const debouncedOnChange = useDebouncedCallback(onChange, { wait: debounce }) + + return ( + { + const nextValue = (e.target as HTMLInputElement).value + setValue(nextValue) + debouncedOnChange(nextValue) + }} + /> + ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render(, rootElement) diff --git a/examples/preact/filters-fuzzy/src/makeData.ts b/examples/preact/filters-fuzzy/src/makeData.ts new file mode 100644 index 0000000000..8d13e339f5 --- /dev/null +++ b/examples/preact/filters-fuzzy/src/makeData.ts @@ -0,0 +1,50 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + id: number + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (num: number): Person => { + return { + id: num, + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((index): Person => { + return { + ...newPerson(index), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/preact/filters-fuzzy/src/vite-env.d.ts b/examples/preact/filters-fuzzy/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/preact/filters-fuzzy/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/preact/filters-fuzzy/tsconfig.json b/examples/preact/filters-fuzzy/tsconfig.json new file mode 100644 index 0000000000..c1e0bb7b6c --- /dev/null +++ b/examples/preact/filters-fuzzy/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/filters-fuzzy/vite.config.ts b/examples/preact/filters-fuzzy/vite.config.ts new file mode 100644 index 0000000000..29b326faf0 --- /dev/null +++ b/examples/preact/filters-fuzzy/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact()], +}) diff --git a/examples/preact/filters/index.html b/examples/preact/filters/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/filters/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/filters/package.json b/examples/preact/filters/package.json new file mode 100644 index 0000000000..9e39d05260 --- /dev/null +++ b/examples/preact/filters/package.json @@ -0,0 +1,25 @@ +{ + "name": "tanstack-preact-table-example-filters", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/match-sorter-utils": "^9.0.0-alpha.4", + "@tanstack/preact-pacer": "^0.22.0", + "@tanstack/preact-table": "^9.0.0-alpha.45", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/preact/filters/src/index.css b/examples/preact/filters/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/preact/filters/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/preact/filters/src/main.tsx b/examples/preact/filters/src/main.tsx new file mode 100644 index 0000000000..a57d2cb741 --- /dev/null +++ b/examples/preact/filters/src/main.tsx @@ -0,0 +1,332 @@ +import { render } from 'preact' +import { useEffect, useMemo, useReducer, useState } from 'preact/hooks' +import './index.css' +import { + columnFilteringFeature, + createColumnHelper, + createFilteredRowModel, + createPaginatedRowModel, + filterFns, + rowPaginationFeature, + tableFeatures, + useTable, +} from '@tanstack/preact-table' +import { useDebouncedCallback } from '@tanstack/preact-pacer/debouncer' +import { makeData } from './makeData' +import type { JSX } from 'preact' +import type { + CellData, + Column, + RowData, + TableFeatures, +} from '@tanstack/preact-table' +import type { Person } from './makeData' + +declare module '@tanstack/preact-table' { + // allows us to define custom properties for our columns + interface ColumnMeta< + TFeatures extends TableFeatures, + TData extends RowData, + TValue extends CellData = CellData, + > { + filterVariant?: 'text' | 'range' | 'select' + } +} + +const _features = tableFeatures({ + columnFilteringFeature, + rowPaginationFeature, +}) + +const columnHelper = createColumnHelper() + +function App() { + const rerender = useReducer(() => ({}), {})[1] + + const columns = useMemo( + () => + columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + }), + columnHelper.accessor((row) => `${row.firstName} ${row.lastName}`, { + id: 'fullName', + header: 'Full Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('age', { + header: () => 'Age', + meta: { + filterVariant: 'range', + }, + }), + columnHelper.accessor('visits', { + header: () => Visits, + meta: { + filterVariant: 'range', + }, + }), + columnHelper.accessor('status', { + header: 'Status', + meta: { + filterVariant: 'select', + }, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + meta: { + filterVariant: 'range', + }, + }), + ]), + [], + ) + + const [data, setData] = useState>(() => makeData(5_000)) + const refreshData = () => setData((_old) => makeData(5_000)) + const stressTest = () => setData((_old) => makeData(200_000)) + + const table = useTable( + { + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), // client side filtering + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data, + debugTable: true, + debugColumns: true, + }, + (state) => state, // default selector + ) + + return ( +
+
+ + +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ) + })} + + ))} + + + {table.getRowModel().rows.map((row) => { + return ( + + {row.getAllCells().map((cell) => { + return ( + + ) + })} + + ) + })} + +
+ {header.isPlaceholder ? null : ( + <> +
+ +
+ {header.column.getCanFilter() ? ( +
+ +
+ ) : null} + + )} +
+ +
+
+
+ + + + + +
Page
+ + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = (e.target as HTMLInputElement).value + ? Number((e.target as HTMLInputElement).value) - 1 + : 0 + table.setPageIndex(page) + }} + className="page-size-input" + /> + + +
+
+ {table.getPrePaginatedRowModel().rows.length.toLocaleString()} Rows +
+
+ +
+
{JSON.stringify(table.state, null, 2)}
+
+ ) +} + +function Filter({ + column, +}: { + column: Column +}) { + const columnFilterValue = column.getFilterValue() + const { filterVariant } = column.columnDef.meta ?? {} + + return filterVariant === 'range' ? ( +
+
+ {/* See faceted column filters example for min max values functionality */} + + column.setFilterValue((old: [number, number] | undefined) => [ + value, + old?.[1], + ]) + } + placeholder={`Min`} + className="filter-input" + /> + + column.setFilterValue((old: [number, number] | undefined) => [ + old?.[0], + value, + ]) + } + placeholder={`Max`} + className="filter-input" + /> +
+
+
+ ) : filterVariant === 'select' ? ( + + ) : ( + column.setFilterValue(value)} + placeholder={`Search...`} + type="text" + value={(columnFilterValue ?? '') as string} + /> + // See faceted column filters example for datalist search suggestions + ) +} + +// A typical debounced input preact component +function DebouncedInput({ + value: initialValue, + onChange, + debounce = 500, + ...props +}: { + value: string | number + onChange: (value: string | number) => void + debounce?: number +} & Record) { + const [value, setValue] = useState(initialValue) + + useEffect(() => { + setValue(initialValue) + }, [initialValue]) + + const debouncedOnChange = useDebouncedCallback(onChange, { wait: debounce }) + + return ( + { + const nextValue = (e.target as HTMLInputElement).value + setValue(nextValue) + debouncedOnChange(nextValue) + }} + /> + ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render(, rootElement) diff --git a/examples/preact/filters/src/makeData.ts b/examples/preact/filters/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/preact/filters/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/preact/filters/src/vite-env.d.ts b/examples/preact/filters/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/preact/filters/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/preact/filters/tsconfig.json b/examples/preact/filters/tsconfig.json new file mode 100644 index 0000000000..c1e0bb7b6c --- /dev/null +++ b/examples/preact/filters/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/filters/vite.config.ts b/examples/preact/filters/vite.config.ts new file mode 100644 index 0000000000..29b326faf0 --- /dev/null +++ b/examples/preact/filters/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact()], +}) diff --git a/examples/preact/grouping/index.html b/examples/preact/grouping/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/grouping/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/grouping/package.json b/examples/preact/grouping/package.json new file mode 100644 index 0000000000..f2b1abd442 --- /dev/null +++ b/examples/preact/grouping/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-preact-table-example-grouping", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/preact-table": "^9.0.0-alpha.45", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/preact/grouping/src/index.css b/examples/preact/grouping/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/preact/grouping/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/preact/grouping/src/main.tsx b/examples/preact/grouping/src/main.tsx new file mode 100644 index 0000000000..adb0ed9f0a --- /dev/null +++ b/examples/preact/grouping/src/main.tsx @@ -0,0 +1,264 @@ +import { useMemo, useReducer, useState } from 'preact/hooks' +import { render } from 'preact' +import './index.css' +import { + aggregationFns, + columnFilteringFeature, + columnGroupingFeature, + createExpandedRowModel, + createFilteredRowModel, + createGroupedRowModel, + createPaginatedRowModel, + createSortedRowModel, + createTableHook, + filterFns, + rowExpandingFeature, + rowPaginationFeature, + rowSortingFeature, + sortFns, +} from '@tanstack/preact-table' +import { makeData } from './makeData' +import type { Person } from './makeData' + +// this example happens to use the createTableHook pattern, but it is not required +const { useAppTable, createAppColumnHelper } = createTableHook({ + _features: { + columnFilteringFeature, + columnGroupingFeature, + rowExpandingFeature, + rowPaginationFeature, + rowSortingFeature, + }, + _rowModels: { + expandedRowModel: createExpandedRowModel(), + filteredRowModel: createFilteredRowModel(filterFns), + groupedRowModel: createGroupedRowModel(aggregationFns), + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, +}) + +const columnHelper = createAppColumnHelper() + +function App() { + const rerender = useReducer(() => ({}), {})[1] + + const columns = useMemo( + () => + columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + cell: (info) => info.getValue(), + /** + * override the value used for row grouping + * (otherwise, defaults to the value derived from accessorKey / accessorFn) + */ + getGroupingValue: (row) => `${row.firstName} ${row.lastName}`, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + header: () => Last Name, + cell: (info) => info.getValue(), + }), + columnHelper.accessor('age', { + header: () => 'Age', + aggregatedCell: ({ getValue }) => + Math.round(getValue() * 100) / 100, + aggregationFn: 'median', + }), + columnHelper.accessor('visits', { + header: () => Visits, + aggregationFn: 'sum', + aggregatedCell: ({ getValue }) => getValue().toLocaleString(), + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + cell: ({ getValue }) => + Math.round(getValue() * 100) / 100 + '%', + aggregationFn: 'mean', + aggregatedCell: ({ getValue }) => + Math.round(getValue() * 100) / 100 + '%', + }), + ]), + [], + ) + + const [data, setData] = useState(() => makeData(10_000)) + const refreshData = () => setData(() => makeData(10_000)) + const stressTest = () => setData(() => makeData(200_000)) + + const table = useAppTable( + { + columns, + data, + debugTable: true, + }, + (state) => state, // default selector + ) + + return ( +
+
+ + +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ) + })} + + ))} + + + {table.getRowModel().rows.map((row) => { + return ( + + {row.getAllCells().map((cell) => { + return ( + + ) + })} + + ) + })} + +
+ {header.isPlaceholder ? null : ( +
+ {header.column.getCanGroup() ? ( + // If the header can be grouped, let's add a toggle + + ) : null}{' '} + +
+ )} +
+ {cell.getIsGrouped() ? ( + // If it's a grouped cell, add an expander and row count + <> + + + ) : cell.getIsAggregated() ? ( + // If the cell is aggregated, use the Aggregated + // renderer for cell + + ) : cell.getIsPlaceholder() ? null : ( // For cells with repeated values, render null + // Otherwise, just render the regular cell + + )} +
+
+
+ + + + + +
Page
+ + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = (e.target as HTMLInputElement).value + ? Number((e.target as HTMLInputElement).value) - 1 + : 0 + table.setPageIndex(page) + }} + className="page-size-input" + /> + + +
+
{table.getRowModel().rows.length.toLocaleString()} Rows
+
+ +
+
{JSON.stringify(table.state, null, 2)}
+
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render(, rootElement) diff --git a/examples/preact/grouping/src/makeData.ts b/examples/preact/grouping/src/makeData.ts new file mode 100644 index 0000000000..95302cdf16 --- /dev/null +++ b/examples/preact/grouping/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/preact/grouping/src/vite-env.d.ts b/examples/preact/grouping/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/preact/grouping/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/preact/grouping/tsconfig.json b/examples/preact/grouping/tsconfig.json new file mode 100644 index 0000000000..c1e0bb7b6c --- /dev/null +++ b/examples/preact/grouping/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/grouping/vite.config.ts b/examples/preact/grouping/vite.config.ts new file mode 100644 index 0000000000..29b326faf0 --- /dev/null +++ b/examples/preact/grouping/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact()], +}) diff --git a/examples/preact/pagination/index.html b/examples/preact/pagination/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/pagination/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/pagination/package.json b/examples/preact/pagination/package.json new file mode 100644 index 0000000000..ce2285101d --- /dev/null +++ b/examples/preact/pagination/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-preact-table-example-pagination", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/preact-store": "^0.13.1", + "@tanstack/preact-table": "^9.0.0-alpha.45", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/preact/pagination/src/index.css b/examples/preact/pagination/src/index.css new file mode 100644 index 0000000000..443b3983a6 --- /dev/null +++ b/examples/preact/pagination/src/index.css @@ -0,0 +1,369 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +button:disabled { + opacity: 0.5; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/preact/pagination/src/main.tsx b/examples/preact/pagination/src/main.tsx new file mode 100644 index 0000000000..7ec2e4e4eb --- /dev/null +++ b/examples/preact/pagination/src/main.tsx @@ -0,0 +1,209 @@ +import { render } from 'preact' +import { useMemo, useReducer, useState } from 'preact/hooks' +import './index.css' +import { + createColumnHelper, + createPaginatedRowModel, + rowPaginationFeature, + tableFeatures, + useTable, +} from '@tanstack/preact-table' +import { makeData } from './makeData' +import type { Person } from './makeData' + +const _features = tableFeatures({ + rowPaginationFeature, +}) + +const columnHelper = createColumnHelper() + +function App() { + const rerender = useReducer(() => ({}), {})[1] + + const columns = useMemo( + () => + columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }), + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + }), + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), + ]), + [], + ) + + const [data, setData] = useState(() => makeData(1_000)) + const refreshData = () => setData(() => makeData(1_000)) + const stressTest = () => setData(() => makeData(200_000)) + + return ( + <> +
+ + +
+ +
+
+ +
+ + ) +} + +function MyTable({ + data, + columns, +}: { + data: Array + columns: ReturnType +}) { + const table = useTable( + { + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data, + debugTable: true, + // no need to pass pageCount or rowCount with client-side pagination as it is calculated automatically + }, + (state) => state, // default selector + ) + + return ( +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ) + })} + + ))} + + + {table.getRowModel().rows.map((row) => { + return ( + + {row.getAllCells().map((cell) => { + return ( + + ) + })} + + ) + })} + +
+
+ +
+
+ +
+
+
+ + + + + +
Page
+ + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = (e.target as HTMLInputElement).value + ? Number((e.target as HTMLInputElement).value) - 1 + : 0 + table.setPageIndex(page) + }} + className="page-size-input" + /> + + +
+
+ Showing {table.getRowModel().rows.length.toLocaleString()} of{' '} + {table.getRowCount().toLocaleString()} Rows +
+
{JSON.stringify(table.state, null, 2)}
+
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render(, rootElement) diff --git a/examples/preact/pagination/src/makeData.ts b/examples/preact/pagination/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/preact/pagination/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/preact/pagination/src/vite-env.d.ts b/examples/preact/pagination/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/preact/pagination/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/preact/pagination/tsconfig.json b/examples/preact/pagination/tsconfig.json new file mode 100644 index 0000000000..c1e0bb7b6c --- /dev/null +++ b/examples/preact/pagination/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/pagination/vite.config.ts b/examples/preact/pagination/vite.config.ts new file mode 100644 index 0000000000..29b326faf0 --- /dev/null +++ b/examples/preact/pagination/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact()], +}) diff --git a/examples/preact/row-pinning/index.html b/examples/preact/row-pinning/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/row-pinning/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/row-pinning/package.json b/examples/preact/row-pinning/package.json new file mode 100644 index 0000000000..871ac8048f --- /dev/null +++ b/examples/preact/row-pinning/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-preact-table-example-row-pinning", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/preact-table": "^9.0.0-alpha.45", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/preact/row-pinning/src/index.css b/examples/preact/row-pinning/src/index.css new file mode 100644 index 0000000000..ac32e94c64 --- /dev/null +++ b/examples/preact/row-pinning/src/index.css @@ -0,0 +1,388 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +thead { + background: lightgray; + margin: 0; + position: sticky; + top: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td { + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td:last-child { + border-right: 0; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +.container { + border: 1px solid lightgray; + height: 500px; + max-width: 900px !important; + overflow: auto; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/preact/row-pinning/src/main.tsx b/examples/preact/row-pinning/src/main.tsx new file mode 100644 index 0000000000..6f2694e06a --- /dev/null +++ b/examples/preact/row-pinning/src/main.tsx @@ -0,0 +1,447 @@ +import { useMemo, useReducer, useState } from 'preact/hooks' +import { render } from 'preact' +import { + columnFilteringFeature, + columnSizingFeature, + createColumnHelper, + createExpandedRowModel, + createFilteredRowModel, + createPaginatedRowModel, + filterFns, + rowExpandingFeature, + rowPaginationFeature, + rowPinningFeature, + tableFeatures, + useTable, +} from '@tanstack/preact-table' +import { makeData } from './makeData' +import type { Person } from './makeData' +import type { + Column, + ExpandedState, + PreactTable, + Row, + RowPinningState, +} from '@tanstack/preact-table' +import './index.css' + +const _features = tableFeatures({ + rowPinningFeature, + rowExpandingFeature, + columnFilteringFeature, + columnSizingFeature, + rowPaginationFeature, +}) + +const columnHelper = createColumnHelper() +function App() { + const rerender = useReducer(() => ({}), {})[1] + + // table states + const [rowPinning, setRowPinning] = useState({ + top: [], + bottom: [], + }) + const [expanded, setExpanded] = useState({}) + + // demo states + const [keepPinnedRows, setKeepPinnedRows] = useState(true) + const [includeLeafRows, setIncludeLeafRows] = useState(true) + const [includeParentRows, setIncludeParentRows] = useState(false) + const [copyPinnedRows, setCopyPinnedRows] = useState(false) + + const columns = useMemo( + () => + columnHelper.columns([ + columnHelper.display({ + id: 'pin', + header: () => 'Pin', + cell: ({ row }) => + row.getIsPinned() ? ( + + ) : ( +
+ + +
+ ), + }), + columnHelper.accessor('firstName', { + header: ({ table }) => ( + <> + {' '} + First Name + + ), + cell: ({ row, getValue }) => ( +
+ <> + {row.getCanExpand() ? ( + + ) : ( + '🔵' + )}{' '} + {getValue()} + +
+ ), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + }), + columnHelper.accessor('age', { + header: () => 'Age', + size: 50, + }), + columnHelper.accessor('visits', { + header: () => Visits, + size: 50, + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + size: 80, + }), + ]), + [includeLeafRows, includeParentRows], + ) + + const [data, setData] = useState(() => makeData(1_000, 2, 2)) + const refreshData = () => setData(() => makeData(1_000, 2, 2)) + const stressTest = () => setData(() => makeData(200_000, 2, 2)) + + const table = useTable( + { + debugTable: true, + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + expandedRowModel: createExpandedRowModel(), + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data, + initialState: { pagination: { pageSize: 20, pageIndex: 0 } }, + state: { + expanded, + rowPinning, + }, + onExpandedChange: setExpanded, + onRowPinningChange: setRowPinning, + getSubRows: (row) => row.subRows, + keepPinnedRows, + debugAll: true, + }, + (state) => state, // default selector + ) + + return ( +
+
+ + +
+
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ) + })} + + ))} + + + {table.getTopRows().map((row) => ( + + ))} + {(copyPinnedRows + ? table.getRowModel().rows + : table.getCenterRows() + ).map((row) => { + return ( + + {row.getAllCells().map((cell) => { + return ( + + ) + })} + + ) + })} + {table.getBottomRows().map((row) => ( + + ))} + +
+ {header.isPlaceholder ? null : ( + <> + + {header.column.getCanFilter() ? ( +
+ +
+ ) : null} + + )} +
+ +
+
+ +
+
+ + + + + +
Page
+ + {(table.store.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = (e.target as HTMLInputElement).value + ? Number((e.target as HTMLInputElement).value) - 1 + : 0 + table.setPageIndex(page) + }} + className="page-size-input" + /> + + +
+
+
+
+
+
+ setKeepPinnedRows(!keepPinnedRows)} + /> + +
+
+ setIncludeLeafRows(!includeLeafRows)} + /> + +
+
+ setIncludeParentRows(!includeParentRows)} + /> + +
+
+ setCopyPinnedRows(!copyPinnedRows)} + /> + +
+
+
+ +
+
{JSON.stringify(rowPinning, null, 2)}
+
+ ) +} + +function PinnedRow({ + row, + table, +}: { + row: Row + table: PreactTable +}) { + return ( + + {row.getAllCells().map((cell) => { + return ( + + + + ) + })} + + ) +} + +function Filter({ + column, + table, +}: { + column: Column + table: PreactTable +}) { + const firstValue = table + .getPreFilteredRowModel() + .flatRows[0]?.getValue(column.id) + + return typeof firstValue === 'number' ? ( +
+ + column.setFilterValue((old: any) => [ + (e.target as HTMLInputElement).value, + old?.[1], + ]) + } + placeholder={`Min`} + className="filter-input" + /> + + column.setFilterValue((old: any) => [ + old?.[0], + (e.target as HTMLInputElement).value, + ]) + } + placeholder={`Max`} + className="filter-input" + /> +
+ ) : ( + + column.setFilterValue((e.target as HTMLInputElement).value) + } + placeholder={`Search...`} + className="filter-select" + /> + ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render(, rootElement) diff --git a/examples/preact/row-pinning/src/makeData.ts b/examples/preact/row-pinning/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/preact/row-pinning/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/preact/row-pinning/src/vite-env.d.ts b/examples/preact/row-pinning/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/preact/row-pinning/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/preact/row-pinning/tsconfig.json b/examples/preact/row-pinning/tsconfig.json new file mode 100644 index 0000000000..c1e0bb7b6c --- /dev/null +++ b/examples/preact/row-pinning/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/row-pinning/vite.config.ts b/examples/preact/row-pinning/vite.config.ts new file mode 100644 index 0000000000..29b326faf0 --- /dev/null +++ b/examples/preact/row-pinning/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact()], +}) diff --git a/examples/preact/row-selection/index.html b/examples/preact/row-selection/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/row-selection/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/row-selection/package.json b/examples/preact/row-selection/package.json new file mode 100644 index 0000000000..1e480c8271 --- /dev/null +++ b/examples/preact/row-selection/package.json @@ -0,0 +1,26 @@ +{ + "name": "tanstack-preact-table-example-row-selection", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/preact-store": "^0.13.1", + "@tanstack/preact-table": "^9.0.0-alpha.45", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "@tanstack/preact-devtools": "^0.10.2", + "@tanstack/preact-table-devtools": "^9.0.0-alpha.43", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/preact/row-selection/src/index.css b/examples/preact/row-selection/src/index.css new file mode 100644 index 0000000000..0a099f6d9f --- /dev/null +++ b/examples/preact/row-selection/src/index.css @@ -0,0 +1,372 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +button:disabled, +button[disabled] { + opacity: 0.5; + cursor: not-allowed; + pointer-events: none; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/preact/row-selection/src/main.tsx b/examples/preact/row-selection/src/main.tsx new file mode 100644 index 0000000000..96fe2eb68f --- /dev/null +++ b/examples/preact/row-selection/src/main.tsx @@ -0,0 +1,400 @@ +import { useEffect, useMemo, useReducer, useRef, useState } from 'preact/hooks' +import { render } from 'preact' +import { + columnFilteringFeature, + createColumnHelper, + createFilteredRowModel, + createPaginatedRowModel, + filterFns, + globalFilteringFeature, + rowPaginationFeature, + rowSelectionFeature, + tableFeatures, + useTable, +} from '@tanstack/preact-table' +import { + tableDevtoolsPlugin, + useTanStackTableDevtools, +} from '@tanstack/preact-table-devtools' +import { TanStackDevtools } from '@tanstack/preact-devtools' +import { useCreateAtom } from '@tanstack/preact-store' +import { makeData } from './makeData' +import type { + Column, + PreactTable, + RowSelectionState, + Table, +} from '@tanstack/preact-table' +import type { JSX } from 'preact' +import type { Person } from './makeData' +import './index.css' + +const _features = tableFeatures({ + rowPaginationFeature, + rowSelectionFeature, + columnFilteringFeature, + globalFilteringFeature, +}) + +const columnHelper = createColumnHelper() + +function App() { + const rerender = useReducer(() => ({}), {})[1] + + const columns = useMemo( + () => + columnHelper.columns([ + columnHelper.display({ + id: 'select', + header: () => { + return ( + + ) + }, + cell: ({ row }) => ( +
+ +
+ ), + }), + columnHelper.accessor('firstName', { + header: 'First Name', + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + header: () => Last Name, + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + }), + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), + ]), + [], + ) + + const [data, setData] = useState(() => makeData(1_000)) + const refreshData = () => setData(() => makeData(1_000)) + const stressTest = () => setData(() => makeData(200_000)) + + const rowSelectionAtom = useCreateAtom({}) + + const table = useTable( + { + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + }, + atoms: { + rowSelection: rowSelectionAtom, + }, + columns, + data, + getRowId: (row) => row.id, + enableRowSelection: true, // enable row selection for all rows + // enableRowSelection: row => row.original.age > 18, // or enable row selection conditionally per row + debugTable: true, + }, + (state) => state, // default selector + ) + + useTanStackTableDevtools(table, 'Row Selection Example') + + return ( + <> +
+
+ + +
+
+ + table.setGlobalFilter((e.target as HTMLInputElement).value) + } + className="summary-panel" + placeholder="Search all columns..." + /> +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ) + })} + + ))} + + + {table.getRowModel().rows.map((row) => { + return ( + + {row.getAllCells().map((cell) => { + return ( + + ) + })} + + ) + })} + + + + + + + +
+ {header.isPlaceholder ? null : ( + <> + + {header.column.getCanFilter() ? ( +
+ +
+ ) : null} + + )} +
+ +
+ + + Page Rows ({table.getRowModel().rows.length.toLocaleString()}) +
+
+
+ + + + + +
Page
+ + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = (e.target as HTMLInputElement).value + ? Number((e.target as HTMLInputElement).value) - 1 + : 0 + table.setPageIndex(page) + }} + className="page-size-input" + /> + + +
+
+
+ <> + {Object.keys(table.state.rowSelection).length.toLocaleString()}{' '} + of{' '} + + {table.getPreFilteredRowModel().rows.length.toLocaleString()} Total + Rows Selected +
+
+
+
+ +
+
+ +
+
+ +
{JSON.stringify(table.state, null, 2)}
+
+
+ + ) +} + +function Filter({ + column, + table, +}: { + column: Column + table: Table +}) { + const firstValue = table + .getPreFilteredRowModel() + .flatRows[0]?.getValue(column.id) + + return typeof firstValue === 'number' ? ( +
+ + column.setFilterValue((old: any) => [ + (e.target as HTMLInputElement).value, + old?.[1], + ]) + } + placeholder={`Min`} + className="filter-input" + /> + + column.setFilterValue((old: any) => [ + old?.[0], + (e.target as HTMLInputElement).value, + ]) + } + placeholder={`Max`} + className="filter-input" + /> +
+ ) : ( + + column.setFilterValue((e.target as HTMLInputElement).value) + } + placeholder={`Search...`} + className="filter-select" + /> + ) +} + +function IndeterminateCheckbox({ + indeterminate, + className = '', + checked, + onChange, + disabled, + ...rest +}: { + indeterminate?: boolean + checked?: boolean + disabled?: boolean + onChange?: (event: Event) => void +} & Record) { + const ref = useRef(null!) + + useEffect(() => { + if (typeof indeterminate === 'boolean') { + ref.current.indeterminate = !checked && indeterminate + } + }, [ref, indeterminate, checked]) + + return ( + + ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render( + <> + + + , + rootElement, +) diff --git a/examples/preact/row-selection/src/makeData.ts b/examples/preact/row-selection/src/makeData.ts new file mode 100644 index 0000000000..c34c43a03e --- /dev/null +++ b/examples/preact/row-selection/src/makeData.ts @@ -0,0 +1,50 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + id: string + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + id: faker.string.uuid(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/preact/row-selection/src/vite-env.d.ts b/examples/preact/row-selection/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/preact/row-selection/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/preact/row-selection/tsconfig.json b/examples/preact/row-selection/tsconfig.json new file mode 100644 index 0000000000..c1e0bb7b6c --- /dev/null +++ b/examples/preact/row-selection/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/row-selection/vite.config.ts b/examples/preact/row-selection/vite.config.ts new file mode 100644 index 0000000000..c16c7375d0 --- /dev/null +++ b/examples/preact/row-selection/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact({ babel: {} })], +}) diff --git a/examples/qwik/filters/.gitignore b/examples/preact/sorting/.gitignore similarity index 100% rename from examples/qwik/filters/.gitignore rename to examples/preact/sorting/.gitignore diff --git a/examples/preact/sorting/README.md b/examples/preact/sorting/README.md new file mode 100644 index 0000000000..56ba98d6c5 --- /dev/null +++ b/examples/preact/sorting/README.md @@ -0,0 +1,15 @@ +# `create-preact` + +

+ +

+ +

Get started using Preact and Vite!

+ +## Getting Started + +- `npm run dev` - Starts a dev server at http://localhost:5173/ + +- `npm run build` - Builds for production, emitting to `dist/` + +- `npm run preview` - Starts a server at http://localhost:4173/ to test production build locally diff --git a/examples/preact/sorting/index.html b/examples/preact/sorting/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/sorting/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/sorting/package.json b/examples/preact/sorting/package.json new file mode 100644 index 0000000000..55fad6e32d --- /dev/null +++ b/examples/preact/sorting/package.json @@ -0,0 +1,25 @@ +{ + "name": "tanstack-preact-table-example-sorting", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/preact-table": "^9.0.0-alpha.45", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "@tanstack/preact-devtools": "^0.10.2", + "@tanstack/preact-table-devtools": "^9.0.0-alpha.43", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/preact/sorting/public/vite.svg b/examples/preact/sorting/public/vite.svg new file mode 100644 index 0000000000..ffcb6bcf53 --- /dev/null +++ b/examples/preact/sorting/public/vite.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/examples/preact/sorting/src/index.css b/examples/preact/sorting/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/preact/sorting/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/preact/sorting/src/main.tsx b/examples/preact/sorting/src/main.tsx new file mode 100644 index 0000000000..94572ef99e --- /dev/null +++ b/examples/preact/sorting/src/main.tsx @@ -0,0 +1,193 @@ +import { render } from 'preact' +import { useMemo, useReducer, useState } from 'preact/hooks' +import './index.css' +import { TanStackDevtools } from '@tanstack/preact-devtools' +import { + tableDevtoolsPlugin, + useTanStackTableDevtools, +} from '@tanstack/preact-table-devtools' +import { + createColumnHelper, + createSortedRowModel, + rowSortingFeature, + sortFns, + tableFeatures, + useTable, +} from '@tanstack/preact-table' +import { makeData } from './makeData' +import type { SortFn, SortingState } from '@tanstack/preact-table' +import type { Person } from './makeData' + +const _features = tableFeatures({ + rowSortingFeature, +}) + +const columnHelper = createColumnHelper() +// custom sorting logic for one of our enum columns +const sortStatusFn: SortFn = (rowA, rowB, _columnId) => { + const statusA = rowA.original.status + const statusB = rowB.original.status + const statusOrder = ['single', 'complicated', 'relationship'] + return statusOrder.indexOf(statusA) - statusOrder.indexOf(statusB) +} + +function App() { + const rerender = useReducer(() => ({}), {})[1] + + const columns = useMemo( + () => + columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + sortUndefined: 'last', + sortDescFirst: false, + }), + columnHelper.accessor('age', { + header: () => 'Age', + }), + columnHelper.accessor('visits', { + header: () => Visits, + sortUndefined: 'last', + }), + columnHelper.accessor('status', { + header: 'Status', + sortFn: sortStatusFn, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + }), + columnHelper.accessor('rank', { + header: 'Rank', + invertSorting: true, + }), + columnHelper.accessor('createdAt', { + header: 'Created At', + }), + ]), + [], + ) + + const [data, setData] = useState(() => makeData(1_000)) + const refreshData = () => setData(() => makeData(1_000)) + const stressTest = () => setData(() => makeData(500_000)) + + // optionally, manage sorting state in your own state management (although preact state causes more re-renders here than necessary) + const [sorting, setSorting] = useState([]) + + console.log('sorting', sorting) + + const table = useTable( + { + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), // client-side sorting + }, + columns, + data, + debugTable: true, + state: { + sorting, + }, + onSortingChange: setSorting, + // no need to pass pageCount or rowCount with client-side pagination as it is calculated automatically + // autoResetPageIndex: false, // turn off page index reset when sorting or filtering - default on/true + // enableMultiSort: false, //Don't allow shift key to sort multiple columns - default on/true + // enableSorting: false, // - default on/true + // enableSortingRemoval: false, //Don't allow - default on/true + // isMultiSortEvent: (e) => true, //Make all clicks multi-sort - default requires `shift` key + // maxMultiSortColCount: 3, // only allow 3 columns to be sorted at once - default is Infinity + }, + (state) => state, // default selector + ) + + useTanStackTableDevtools(table, 'Sorting Example') + + return ( +
+
+ + +
+ <> +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ) + })} + + ))} + + + {table + .getRowModel() + .rows.slice(0, 10) + .map((row) => { + return ( + + {row.getAllCells().map((cell) => { + return ( + + ) + })} + + ) + })} + +
+ {header.isPlaceholder ? null : ( +
+ + {{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null} +
+ )} +
+ +
+
{table.getRowModel().rows.length.toLocaleString()} Rows
+
+ +
+ {/* Store mode: full state for debugging */} +
{JSON.stringify(table.state, null, 2)}
+ +
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render( + <> + + + , + rootElement, +) diff --git a/examples/preact/sorting/src/makeData.ts b/examples/preact/sorting/src/makeData.ts new file mode 100644 index 0000000000..fc070cd5d2 --- /dev/null +++ b/examples/preact/sorting/src/makeData.ts @@ -0,0 +1,52 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string | undefined + age: number + visits: number | undefined + progress: number + status: 'relationship' | 'complicated' | 'single' + rank: number + createdAt: Date + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: Math.random() < 0.1 ? undefined : faker.person.lastName(), + age: faker.number.int(40), + visits: Math.random() < 0.1 ? undefined : faker.number.int(1000), + progress: faker.number.int(100), + createdAt: faker.date.anytime(), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + rank: faker.number.int(100), + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/preact/sorting/src/vite-env.d.ts b/examples/preact/sorting/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/preact/sorting/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/preact/sorting/tsconfig.json b/examples/preact/sorting/tsconfig.json new file mode 100644 index 0000000000..c1e0bb7b6c --- /dev/null +++ b/examples/preact/sorting/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/sorting/vite.config.ts b/examples/preact/sorting/vite.config.ts new file mode 100644 index 0000000000..345971f41c --- /dev/null +++ b/examples/preact/sorting/vite.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + // Force the Babel transform path so dev mode avoids the preset's CJS + // hook-name transform, which currently trips over ESM-only `zimmerframe`. + preact({ babel: {} }), + ], +}) diff --git a/examples/preact/sub-components/index.html b/examples/preact/sub-components/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/sub-components/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/sub-components/package.json b/examples/preact/sub-components/package.json new file mode 100644 index 0000000000..5209e9070a --- /dev/null +++ b/examples/preact/sub-components/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-preact-table-example-sub-components", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/preact-table": "^9.0.0-alpha.45", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/preact/sub-components/src/index.css b/examples/preact/sub-components/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/preact/sub-components/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/preact/sub-components/src/main.tsx b/examples/preact/sub-components/src/main.tsx new file mode 100644 index 0000000000..e632c10ff8 --- /dev/null +++ b/examples/preact/sub-components/src/main.tsx @@ -0,0 +1,198 @@ +import { useState } from 'preact/hooks' +import { Fragment, render } from 'preact' +import './index.css' +import { + createColumnHelper, + createExpandedRowModel, + rowExpandingFeature, + tableFeatures, + useTable, +} from '@tanstack/preact-table' +import { makeData } from './makeData' +import type { + ColumnDef, + Row, + RowData, + TableFeatures, +} from '@tanstack/preact-table' +import type { Person } from './makeData' +import type { JSX } from 'preact' + +const _features = tableFeatures({ + rowExpandingFeature, +}) + +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.display({ + id: 'expander', + header: () => null, + cell: ({ row }) => { + return row.getCanExpand() ? ( + + ) : ( + '🔵' + ) + }, + }), + columnHelper.accessor('firstName', { + header: 'First Name', + cell: ({ row, getValue }) => ( +
+ {getValue()} +
+ ), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }), + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + }), + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), +]) + +type TableProps = { + data: Array + columns: Array> + renderSubComponent: (props: { row: Row }) => JSX.Element + getRowCanExpand: (row: Row) => boolean +} + +function Table({ + columns, + data, + getRowCanExpand, + renderSubComponent, +}: TableProps): JSX.Element { + const table = useTable( + { + debugTable: true, + _features, + _rowModels: { + expandedRowModel: createExpandedRowModel(), + }, + columns, + data, + getRowCanExpand, + }, + (state) => state, // default selector + ) + + return ( +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ) + })} + + ))} + + + {table.getRowModel().rows.map((row) => { + return ( + + + {/* first row is a normal row */} + {row.getAllCells().map((cell) => { + return ( + + ) + })} + + {row.getIsExpanded() && ( + + {/* 2nd row is a custom 1 cell row */} + + + )} + + ) + })} + +
+ {header.isPlaceholder ? null : ( +
+ +
+ )} +
+ +
+ {renderSubComponent({ row })} +
+
+
{table.getRowModel().rows.length.toLocaleString()} Rows
+
+ ) +} + +const renderSubComponent = ({ + row, +}: { + row: Row +}) => { + return ( +
+      {JSON.stringify(row.original, null, 2)}
+    
+ ) +} + +function App() { + const [data, setData] = useState(() => makeData(20)) + const refreshData = () => setData(makeData(20)) + const stressTest = () => setData(makeData(1_000)) + + return ( + <> +
+ + +
+ true} + renderSubComponent={renderSubComponent} + /> + + ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render(, rootElement) diff --git a/examples/preact/sub-components/src/makeData.ts b/examples/preact/sub-components/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/preact/sub-components/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/preact/sub-components/src/vite-env.d.ts b/examples/preact/sub-components/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/preact/sub-components/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/preact/sub-components/tsconfig.json b/examples/preact/sub-components/tsconfig.json new file mode 100644 index 0000000000..c1e0bb7b6c --- /dev/null +++ b/examples/preact/sub-components/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/sub-components/vite.config.ts b/examples/preact/sub-components/vite.config.ts new file mode 100644 index 0000000000..29b326faf0 --- /dev/null +++ b/examples/preact/sub-components/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact()], +}) diff --git a/examples/preact/with-tanstack-query/index.html b/examples/preact/with-tanstack-query/index.html new file mode 100644 index 0000000000..fff0f71352 --- /dev/null +++ b/examples/preact/with-tanstack-query/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Preact + + +
+ + + diff --git a/examples/preact/with-tanstack-query/package.json b/examples/preact/with-tanstack-query/package.json new file mode 100644 index 0000000000..88efec36ac --- /dev/null +++ b/examples/preact/with-tanstack-query/package.json @@ -0,0 +1,25 @@ +{ + "name": "tanstack-preact-table-example-with-tanstack-query", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/preact-query": "^5.100.9", + "@tanstack/preact-store": "^0.13.1", + "@tanstack/preact-table": "^9.0.0-alpha.45", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/preact/with-tanstack-query/src/fetchData.ts b/examples/preact/with-tanstack-query/src/fetchData.ts new file mode 100644 index 0000000000..eefb050404 --- /dev/null +++ b/examples/preact/with-tanstack-query/src/fetchData.ts @@ -0,0 +1,67 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} + +const data = makeData(10000) + +export async function fetchData(options: { + pageIndex: number + pageSize: number +}) { + // Simulate some network latency + await new Promise((r) => setTimeout(r, 500)) + + return { + rows: data.slice( + options.pageIndex * options.pageSize, + (options.pageIndex + 1) * options.pageSize, + ), + pageCount: Math.ceil(data.length / options.pageSize), + rowCount: data.length, + } +} diff --git a/examples/preact/with-tanstack-query/src/index.css b/examples/preact/with-tanstack-query/src/index.css new file mode 100644 index 0000000000..443b3983a6 --- /dev/null +++ b/examples/preact/with-tanstack-query/src/index.css @@ -0,0 +1,369 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +button:disabled { + opacity: 0.5; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/preact/with-tanstack-query/src/main.tsx b/examples/preact/with-tanstack-query/src/main.tsx new file mode 100644 index 0000000000..fb645ef10d --- /dev/null +++ b/examples/preact/with-tanstack-query/src/main.tsx @@ -0,0 +1,204 @@ +import { useMemo, useReducer } from 'preact/hooks' +import { render } from 'preact' +import { + QueryClient, + QueryClientProvider, + keepPreviousData, + useQuery, +} from '@tanstack/preact-query' +import { useCreateAtom, useSelector } from '@tanstack/preact-store' +import './index.css' +import { + createColumnHelper, + rowPaginationFeature, + tableFeatures, + useTable, +} from '@tanstack/preact-table' +import { fetchData } from './fetchData' +import type { PaginationState } from '@tanstack/preact-table' +import type { Person } from './fetchData' + +const queryClient = new QueryClient() + +const _features = tableFeatures({ + rowPaginationFeature, +}) + +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('lastName', { + header: 'Last Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('age', { + header: 'Age', + }), + columnHelper.accessor('visits', { + header: 'Visits', + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + }), +]) + +function App() { + const rerender = useReducer(() => ({}), {})[1] + + // Create a stable external atom for the pagination slice. + const paginationAtom = useCreateAtom({ + pageIndex: 0, + pageSize: 10, + }) + + // Subscribe to the atom for reactive updates. + const pagination = useSelector(paginationAtom, (s) => s) + + const dataQuery = useQuery({ + queryKey: ['data', pagination], + queryFn: () => fetchData(pagination), + placeholderData: keepPreviousData, // don't have 0 rows flash while changing pages/loading next page + }) + + const defaultData = useMemo(() => [], []) + + const table = useTable( + { + _features, + _rowModels: {}, + columns, + data: dataQuery.data?.rows ?? defaultData, + rowCount: dataQuery.data?.rowCount, + atoms: { + pagination: paginationAtom, + }, + manualPagination: true, // we're doing manual "server-side" pagination + debugTable: true, + }, + (state) => state, // default selector + ) + + return ( +
+
+
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getAllCells().map((cell) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( + + )} +
+ +
+
+
+ + + + + +
Page
+ + {(pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = (e.target as HTMLInputElement).value + ? Number((e.target as HTMLInputElement).value) - 1 + : 0 + table.setPageIndex(page) + }} + className="page-size-input" + /> + + + {dataQuery.isFetching ? 'Loading...' : null} +
+
+ Showing {table.getRowModel().rows.length.toLocaleString()} of{' '} + {dataQuery.data?.rowCount.toLocaleString()} Rows +
+
+ +
+
{JSON.stringify({ pagination }, null, 2)}
+
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +render( + + + , + rootElement, +) diff --git a/examples/preact/with-tanstack-query/src/vite-env.d.ts b/examples/preact/with-tanstack-query/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/preact/with-tanstack-query/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/preact/with-tanstack-query/tsconfig.json b/examples/preact/with-tanstack-query/tsconfig.json new file mode 100644 index 0000000000..c1e0bb7b6c --- /dev/null +++ b/examples/preact/with-tanstack-query/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true, + "paths": { + "react": ["./node_modules/preact/compat/"], + "react-dom": ["./node_modules/preact/compat/"] + } + }, + "include": [ + "node_modules/vite/client.d.ts", + "src/**/*", + "vite.config.js", + "vite.config.ts" + ], + "exclude": ["dist/**/*", "node_modules"] +} diff --git a/examples/preact/with-tanstack-query/vite.config.ts b/examples/preact/with-tanstack-query/vite.config.ts new file mode 100644 index 0000000000..29b326faf0 --- /dev/null +++ b/examples/preact/with-tanstack-query/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [preact()], +}) diff --git a/examples/qwik/basic/README.md b/examples/qwik/basic/README.md deleted file mode 100644 index c813736fcc..0000000000 --- a/examples/qwik/basic/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# Qwik + Vite - -## Qwik in CSR mode - -This starter is using a pure CSR (Client Side Rendering) mode. This means, that the application is fully bootstrapped in the browser. Most of Qwik innovations however take advantage of SSR (Server Side Rendering) mode. - -```ts -export default defineConfig({ - plugins: [ - qwikVite({ - csr: true, - }), - ], -}) -``` - -Use `npm create qwik@latest` to create a full production ready Qwik application, using SSR and [QwikCity](https://qwik.builder.io/docs/qwikcity/), our server-side metaframwork. - -## Usage - -```bash -$ npm install # or pnpm install or yarn install -``` - -Learn more on the [Qwik Website](https://qwik.builder.io) and join our community on our [Discord](https://qwik.builder.io/chat) - -## Available Scripts - -In the project directory, you can run: - -### `npm run dev` - -Runs the app in the development mode.
-Open [http://localhost:5173](http://localhost:5173) to view it in the browser. - -### `npm run build` - -Builds the app for production to the `dist` folder.
diff --git a/examples/qwik/basic/index.html b/examples/qwik/basic/index.html deleted file mode 100644 index 9a3384e789..0000000000 --- a/examples/qwik/basic/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - Vite + Qwik + TS - - -
- - - diff --git a/examples/qwik/basic/package.json b/examples/qwik/basic/package.json deleted file mode 100644 index 73ab63ca9a..0000000000 --- a/examples/qwik/basic/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "tanstack-table-example-qwik-basic", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "serve dist" - }, - "devDependencies": { - "@builder.io/qwik": "^1.6.0", - "serve": "^14.2.3", - "typescript": "5.4.5", - "vite": "^5.3.2" - }, - "dependencies": { - "@tanstack/qwik-table": "^8.20.5" - } -} diff --git a/examples/qwik/basic/src/index.css b/examples/qwik/basic/src/index.css deleted file mode 100644 index 43c09e0f6b..0000000000 --- a/examples/qwik/basic/src/index.css +++ /dev/null @@ -1,26 +0,0 @@ -html { - font-family: sans-serif; - font-size: 14px; -} - -table { - border: 1px solid lightgray; -} - -tbody { - border-bottom: 1px solid lightgray; -} - -th { - border-bottom: 1px solid lightgray; - border-right: 1px solid lightgray; - padding: 2px 4px; -} - -tfoot { - color: gray; -} - -tfoot th { - font-weight: normal; -} diff --git a/examples/qwik/basic/src/main.tsx b/examples/qwik/basic/src/main.tsx deleted file mode 100644 index c25919e845..0000000000 --- a/examples/qwik/basic/src/main.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import '@builder.io/qwik/qwikloader.js' -import { render, component$ } from '@builder.io/qwik' - -import './index.css' - -import { - createColumnHelper, - getCoreRowModel, - flexRender, - useQwikTable, -} from '@tanstack/qwik-table' - -type Person = { - firstName: string - lastName: string - age: number - visits: number - status: string - progress: number -} - -const defaultData: Person[] = [ - { - firstName: 'tanner', - lastName: 'linsley', - age: 24, - visits: 100, - status: 'In Relationship', - progress: 50, - }, - { - firstName: 'tandy', - lastName: 'miller', - age: 40, - visits: 40, - status: 'Single', - progress: 80, - }, - { - firstName: 'joe', - lastName: 'dirte', - age: 45, - visits: 20, - status: 'Complicated', - progress: 10, - }, - { - firstName: 'Anxhi', - lastName: 'Rroshi', - age: 21, - visits: 1, - status: 'Unknown', - progress: 99, - }, -] - -const columnHelper = createColumnHelper() - -const columns = [ - columnHelper.accessor('firstName', { - cell: info => info.getValue(), - footer: info => info.column.id, - }), - columnHelper.accessor(row => row.lastName, { - id: 'lastName', - cell: info => {info.getValue()}, - header: () => Last Name, - footer: info => info.column.id, - }), - columnHelper.accessor('age', { - header: () => 'Age', - cell: info => info.renderValue(), - footer: info => info.column.id, - }), - columnHelper.accessor('visits', { - header: () => Visits, - footer: info => info.column.id, - }), - columnHelper.accessor('status', { - header: 'Status', - footer: info => info.column.id, - }), - columnHelper.accessor('progress', { - header: 'Profile Progress', - footer: info => info.column.id, - }), -] - -const App = component$(() => { - const table = useQwikTable({ - columns, - data: defaultData, - getCoreRowModel: getCoreRowModel(), - enableSorting: true, - }) - - return ( -
- - - {table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(header => ( - - ))} - - ))} - - - {table.getRowModel().rows.map(row => ( - - {row.getVisibleCells().map(cell => ( - - ))} - - ))} - - - {table.getFooterGroups().map(footerGroup => ( - - {footerGroup.headers.map(header => ( - - ))} - - ))} - -
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} -
- {flexRender(cell.column.columnDef.cell, cell.getContext())} -
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.footer, - header.getContext() - )} -
-
- ) -}) - -render(document.getElementById('app') as HTMLElement, ) diff --git a/examples/qwik/basic/tsconfig.json b/examples/qwik/basic/tsconfig.json deleted file mode 100644 index 9a262f041f..0000000000 --- a/examples/qwik/basic/tsconfig.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "module": "ESNext", - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "jsxImportSource": "@builder.io/qwik", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"], - "references": [{ "path": "./tsconfig.node.json" }] -} diff --git a/examples/qwik/basic/tsconfig.node.json b/examples/qwik/basic/tsconfig.node.json deleted file mode 100644 index 97ede7ee6f..0000000000 --- a/examples/qwik/basic/tsconfig.node.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "composite": true, - "skipLibCheck": true, - "module": "ESNext", - "moduleResolution": "bundler", - "allowSyntheticDefaultImports": true, - "strict": true - }, - "include": ["vite.config.ts"] -} diff --git a/examples/qwik/basic/vite.config.ts b/examples/qwik/basic/vite.config.ts deleted file mode 100644 index cabd66b013..0000000000 --- a/examples/qwik/basic/vite.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { defineConfig } from 'vite' -import { qwikVite } from '@builder.io/qwik/optimizer' - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [ - qwikVite({ - csr: true, - }), - ], -}) diff --git a/examples/qwik/filters/README.md b/examples/qwik/filters/README.md deleted file mode 100644 index c813736fcc..0000000000 --- a/examples/qwik/filters/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# Qwik + Vite - -## Qwik in CSR mode - -This starter is using a pure CSR (Client Side Rendering) mode. This means, that the application is fully bootstrapped in the browser. Most of Qwik innovations however take advantage of SSR (Server Side Rendering) mode. - -```ts -export default defineConfig({ - plugins: [ - qwikVite({ - csr: true, - }), - ], -}) -``` - -Use `npm create qwik@latest` to create a full production ready Qwik application, using SSR and [QwikCity](https://qwik.builder.io/docs/qwikcity/), our server-side metaframwork. - -## Usage - -```bash -$ npm install # or pnpm install or yarn install -``` - -Learn more on the [Qwik Website](https://qwik.builder.io) and join our community on our [Discord](https://qwik.builder.io/chat) - -## Available Scripts - -In the project directory, you can run: - -### `npm run dev` - -Runs the app in the development mode.
-Open [http://localhost:5173](http://localhost:5173) to view it in the browser. - -### `npm run build` - -Builds the app for production to the `dist` folder.
diff --git a/examples/qwik/filters/index.html b/examples/qwik/filters/index.html deleted file mode 100644 index 9a3384e789..0000000000 --- a/examples/qwik/filters/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - Vite + Qwik + TS - - -
- - - diff --git a/examples/qwik/filters/package.json b/examples/qwik/filters/package.json deleted file mode 100644 index 4d62e5619a..0000000000 --- a/examples/qwik/filters/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "tanstack-table-example-qwik-filters", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "serve dist" - }, - "devDependencies": { - "@builder.io/qwik": "^1.6.0", - "@faker-js/faker": "^8.4.1", - "serve": "^14.2.3", - "typescript": "5.4.5", - "vite": "^5.3.2" - }, - "dependencies": { - "@tanstack/qwik-table": "^8.20.5", - "@tanstack/match-sorter-utils": "^8.19.4" - } -} diff --git a/examples/qwik/filters/src/index.css b/examples/qwik/filters/src/index.css deleted file mode 100644 index 43c09e0f6b..0000000000 --- a/examples/qwik/filters/src/index.css +++ /dev/null @@ -1,26 +0,0 @@ -html { - font-family: sans-serif; - font-size: 14px; -} - -table { - border: 1px solid lightgray; -} - -tbody { - border-bottom: 1px solid lightgray; -} - -th { - border-bottom: 1px solid lightgray; - border-right: 1px solid lightgray; - padding: 2px 4px; -} - -tfoot { - color: gray; -} - -tfoot th { - font-weight: normal; -} diff --git a/examples/qwik/filters/src/main.tsx b/examples/qwik/filters/src/main.tsx deleted file mode 100644 index 95063efa5f..0000000000 --- a/examples/qwik/filters/src/main.tsx +++ /dev/null @@ -1,362 +0,0 @@ -import '@builder.io/qwik/qwikloader.js' - -import { $, render, useSignal } from '@builder.io/qwik' -import './index.css' - -import { - createColumnHelper, - getCoreRowModel, - getPaginationRowModel, - getSortedRowModel, - flexRender, - useQwikTable, - ColumnFiltersState, - SortingFn, - FilterFn, - getFilteredRowModel, - getFacetedRowModel, - getFacetedUniqueValues, - getFacetedMinMaxValues, - Column, - Table, - sortingFns, -} from '@tanstack/qwik-table' - -import { component$ } from '@builder.io/qwik' - -import { - rankItem, - compareItems, - RankingInfo, -} from '@tanstack/match-sorter-utils' - -declare module '@tanstack/qwik-table' { - interface FilterFns { - fuzzy: FilterFn - } - interface FilterMeta { - itemRank: RankingInfo - } -} - -const fuzzyFilter: FilterFn = (row, columnId, value, addMeta) => { - // Rank the item - const itemRank = rankItem(row.getValue(columnId), value) - - // Store the itemRank info - addMeta({ - itemRank, - }) - - // Return if the item should be filtered in/out - return itemRank.passed -} - -const fuzzySort: SortingFn = (rowA, rowB, columnId) => { - let dir = 0 - - // Only sort by rank if the column has ranking information - if (rowA.columnFiltersMeta[columnId]) { - dir = compareItems( - rowA.columnFiltersMeta[columnId]?.itemRank!, - rowB.columnFiltersMeta[columnId]?.itemRank! - ) - } - - // Provide an alphanumeric fallback for when the item ranks are equal - return dir === 0 ? sortingFns.alphanumeric(rowA, rowB, columnId) : dir -} - -type Person = { - firstName: string - lastName: string - age: number - visits: number - status: string - progress: number -} - -const defaultData: Person[] = [ - { - firstName: 'tanner', - lastName: 'linsley', - age: 24, - visits: 100, - status: 'In Relationship', - progress: 50, - }, - { - firstName: 'tandy', - lastName: 'miller', - age: 40, - visits: 40, - status: 'Single', - progress: 80, - }, - { - firstName: 'joe', - lastName: 'dirte', - age: 45, - visits: 20, - status: 'Complicated', - progress: 10, - }, - { - firstName: 'Anxhi', - lastName: 'Rroshi', - age: 21, - visits: 1, - status: 'Unknown', - progress: 99, - }, -] - -const columnHelper = createColumnHelper() - -const columns = [ - columnHelper.group({ - header: 'Name', - footer: props => props.column.id, - columns: [ - columnHelper.accessor('firstName', { - cell: info => info.getValue(), - footer: props => props.column.id, - }), - columnHelper.accessor(row => row.lastName, { - id: 'lastName', - cell: info => info.getValue(), - header: () => Last Name, - footer: props => props.column.id, - sortingFn: fuzzySort, - }), - columnHelper.accessor(row => `${row.firstName} ${row.lastName}`, { - id: 'fullName', - header: 'Full Name', - cell: info => info.getValue(), - footer: props => props.column.id, - sortingFn: fuzzySort, - }), - ], - }), - columnHelper.group({ - header: 'Info', - footer: props => props.column.id, - columns: [ - columnHelper.accessor('age', { - header: () => 'Age', - footer: props => props.column.id, - }), - columnHelper.group({ - header: 'More Info', - columns: [ - columnHelper.accessor('visits', { - header: () => Visits, - footer: props => props.column.id, - }), - columnHelper.accessor('status', { - header: 'Status', - footer: props => props.column.id, - }), - columnHelper.accessor('progress', { - header: 'Profile Progress', - footer: props => props.column.id, - }), - ], - }), - ], - }), -] - -const App = component$(() => { - const columnFilters = useSignal([]) - const globalFilter = useSignal('') - - const table = useQwikTable({ - data: defaultData, - columns, - enableSorting: true, - filterFns: { - fuzzy: fuzzyFilter, - }, - state: { - columnFilters: columnFilters.value, - globalFilter: globalFilter.value, - }, - onColumnFiltersChange: updater => { - const updated = - updater instanceof Function ? updater(columnFilters.value) : updater - columnFilters.value = updated - }, - onGlobalFilterChange: updater => { - const updated = updater(globalFilter.value) - globalFilter.value = updated - }, - globalFilterFn: fuzzyFilter, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getSortedRowModel: getSortedRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getFacetedRowModel: getFacetedRowModel(), - getFacetedUniqueValues: getFacetedUniqueValues(), - getFacetedMinMaxValues: getFacetedMinMaxValues(), - debugTable: true, - debugHeaders: true, - debugColumns: false, - }) - - return ( -
-
- { - globalFilter.value = (e.target as HTMLInputElement).value - })} - class="p-2 font-lg shadow border border-block" - placeholder="Search all columns..." - /> -
- - - {table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(header => { - return ( - - ) - })} - - ))} - - - {table.getRowModel().rows.map(row => { - return ( - - {row.getVisibleCells().map(cell => ( - - ))} - - ) - })} - -
- {header.isPlaceholder ? null : ( - <> -
- {flexRender( - header.column.columnDef.header, - header.getContext() - )} - {{ - asc: ' 🔼', - desc: ' 🔽', - }[header.column.getIsSorted() as string] ?? null} -
- {header.column.getCanFilter() ? ( -
- -
- ) : null} - - )} -
- {flexRender(cell.column.columnDef.cell, cell.getContext())} -
-
{table.getPrePaginationRowModel().rows.length} Rows
-
{JSON.stringify(table.getState(), null, 2)}
-
- ) -}) - -function Filter({ - column, - table, -}: { - column: Column - table: Table -}) { - const { id } = column - const firstValue = table - .getPreFilteredRowModel() - .flatRows[0]?.getValue(column.id) - - const columnFilterValue = column.getFilterValue() - - const sortedUniqueValues = - typeof firstValue === 'number' - ? [] - : Array.from(column.getFacetedUniqueValues().keys()).sort() - - return typeof firstValue === 'number' ? ( -
-
- { - const value = Number((e.target as HTMLInputElement).value) - const myCol = table.getColumn(id) - myCol?.setFilterValue((old: [number, number]) => [value, old?.[1]]) - })} - placeholder={`Min ${ - column.getFacetedMinMaxValues()?.[0] - ? `(${column.getFacetedMinMaxValues()?.[0]})` - : '' - }`} - class="w-24 border shadow rounded" - /> - { - const value = Number((e.target as HTMLInputElement).value) - const myCol = table.getColumn(id) - myCol?.setFilterValue((old: [number, number]) => [old?.[0], value]) - })} - placeholder={`Max ${ - column.getFacetedMinMaxValues()?.[1] - ? `(${column.getFacetedMinMaxValues()?.[1]})` - : '' - }`} - class="w-24 border shadow rounded" - /> -
-
-
- ) : ( - <> - - {sortedUniqueValues.slice(0, 5000).map((value: any) => ( - - { - const value = (e.target as HTMLInputElement).value - const myCol = table.getColumn(id) - myCol?.setFilterValue(value) - // column.setFilterValue(e.target.value) - })} - placeholder={`Search... (${column.getFacetedUniqueValues().size})`} - // class="w-36 border shadow rounded" - // list={column.id + 'list'} - /> -
- - ) -} - -render(document.getElementById('app') as HTMLElement, ) diff --git a/examples/qwik/filters/tsconfig.json b/examples/qwik/filters/tsconfig.json deleted file mode 100644 index 9a262f041f..0000000000 --- a/examples/qwik/filters/tsconfig.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "module": "ESNext", - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "jsxImportSource": "@builder.io/qwik", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"], - "references": [{ "path": "./tsconfig.node.json" }] -} diff --git a/examples/qwik/filters/tsconfig.node.json b/examples/qwik/filters/tsconfig.node.json deleted file mode 100644 index 97ede7ee6f..0000000000 --- a/examples/qwik/filters/tsconfig.node.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "composite": true, - "skipLibCheck": true, - "module": "ESNext", - "moduleResolution": "bundler", - "allowSyntheticDefaultImports": true, - "strict": true - }, - "include": ["vite.config.ts"] -} diff --git a/examples/qwik/filters/vite.config.ts b/examples/qwik/filters/vite.config.ts deleted file mode 100644 index cabd66b013..0000000000 --- a/examples/qwik/filters/vite.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { defineConfig } from 'vite' -import { qwikVite } from '@builder.io/qwik/optimizer' - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [ - qwikVite({ - csr: true, - }), - ], -}) diff --git a/examples/qwik/row-selection/README.md b/examples/qwik/row-selection/README.md deleted file mode 100644 index c813736fcc..0000000000 --- a/examples/qwik/row-selection/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# Qwik + Vite - -## Qwik in CSR mode - -This starter is using a pure CSR (Client Side Rendering) mode. This means, that the application is fully bootstrapped in the browser. Most of Qwik innovations however take advantage of SSR (Server Side Rendering) mode. - -```ts -export default defineConfig({ - plugins: [ - qwikVite({ - csr: true, - }), - ], -}) -``` - -Use `npm create qwik@latest` to create a full production ready Qwik application, using SSR and [QwikCity](https://qwik.builder.io/docs/qwikcity/), our server-side metaframwork. - -## Usage - -```bash -$ npm install # or pnpm install or yarn install -``` - -Learn more on the [Qwik Website](https://qwik.builder.io) and join our community on our [Discord](https://qwik.builder.io/chat) - -## Available Scripts - -In the project directory, you can run: - -### `npm run dev` - -Runs the app in the development mode.
-Open [http://localhost:5173](http://localhost:5173) to view it in the browser. - -### `npm run build` - -Builds the app for production to the `dist` folder.
diff --git a/examples/qwik/row-selection/index.html b/examples/qwik/row-selection/index.html deleted file mode 100644 index 9a3384e789..0000000000 --- a/examples/qwik/row-selection/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - Vite + Qwik + TS - - -
- - - diff --git a/examples/qwik/row-selection/package.json b/examples/qwik/row-selection/package.json deleted file mode 100644 index 900aed03c1..0000000000 --- a/examples/qwik/row-selection/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "tanstack-table-example-qwik-row-selection", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "serve dist" - }, - "devDependencies": { - "@builder.io/qwik": "^1.6.0", - "@faker-js/faker": "^8.4.1", - "serve": "^14.2.3", - "typescript": "5.4.5", - "vite": "^5.3.2" - }, - "dependencies": { - "@tanstack/qwik-table": "^8.20.5" - } -} diff --git a/examples/qwik/row-selection/src/index.css b/examples/qwik/row-selection/src/index.css deleted file mode 100644 index 43c09e0f6b..0000000000 --- a/examples/qwik/row-selection/src/index.css +++ /dev/null @@ -1,26 +0,0 @@ -html { - font-family: sans-serif; - font-size: 14px; -} - -table { - border: 1px solid lightgray; -} - -tbody { - border-bottom: 1px solid lightgray; -} - -th { - border-bottom: 1px solid lightgray; - border-right: 1px solid lightgray; - padding: 2px 4px; -} - -tfoot { - color: gray; -} - -tfoot th { - font-weight: normal; -} diff --git a/examples/qwik/row-selection/src/main.tsx b/examples/qwik/row-selection/src/main.tsx deleted file mode 100644 index 3c24e842a7..0000000000 --- a/examples/qwik/row-selection/src/main.tsx +++ /dev/null @@ -1,230 +0,0 @@ -import '@builder.io/qwik/qwikloader.js' - -import { $, render, useSignal } from '@builder.io/qwik' -import './index.css' - -import { - createColumnHelper, - getCoreRowModel, - getSortedRowModel, - flexRender, - useQwikTable, - getFilteredRowModel, - ColumnDef, -} from '@tanstack/qwik-table' - -import { component$ } from '@builder.io/qwik' - -type Person = { - firstName: string - lastName: string - age: number - visits: number - status: string - progress: number -} - -const defaultData: Person[] = [ - { - firstName: 'tanner', - lastName: 'linsley', - age: 24, - visits: 100, - status: 'In Relationship', - progress: 50, - }, - { - firstName: 'tandy', - lastName: 'miller', - age: 40, - visits: 40, - status: 'Single', - progress: 80, - }, - { - firstName: 'joe', - lastName: 'dirte', - age: 45, - visits: 20, - status: 'Complicated', - progress: 10, - }, - { - firstName: 'Anxhi', - lastName: 'Rroshi', - age: 21, - visits: 1, - status: 'Unknown', - progress: 99, - }, -] - -const columnHelper = createColumnHelper() -const columns: ColumnDef[] = [ - { - id: 'select', - header: ({ table }) => ( - { - console.log('toggleAllRowsSelected') - table.toggleAllRowsSelected() - }), - }} - /> - ), - cell: ({ row, table }) => { - const { id } = row - return ( -
- { - // TODO: getting row instance from table works, but how can we call getToggleSelectedHandler() withour getting qwik qrl error? - const row = table.getRow(id) - row?.toggleSelected() - }), - }} - /> -
- ) - }, - }, - columnHelper.accessor('firstName', { - cell: info => info.getValue(), - footer: props => props.column.id, - }), - columnHelper.accessor(row => row.lastName, { - id: 'lastName', - cell: info => info.getValue(), - header: () => Last Name, - footer: props => props.column.id, - }), - columnHelper.accessor('age', { - header: () => 'Age', - footer: props => props.column.id, - }), - columnHelper.accessor('visits', { - header: () => Visits, - footer: props => props.column.id, - }), - columnHelper.accessor('status', { - header: 'Status', - footer: props => props.column.id, - }), - columnHelper.accessor('progress', { - header: 'Profile Progress', - footer: props => props.column.id, - }), -] - -const App = component$(() => { - const rowSelection = useSignal({}) - - const table = useQwikTable({ - columns, - data: defaultData, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), - enableSorting: true, - onRowSelectionChange: updater => { - rowSelection.value = - updater instanceof Function ? updater(rowSelection.value) : updater - }, - state: { - rowSelection: rowSelection.value, - }, - getFilteredRowModel: getFilteredRowModel(), - enableRowSelection: true, - }) - - return ( -
- - - {table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(header => { - const { column } = header - const id = column.id - return ( - - ) - })} - - ))} - - - - {table.getRowModel().rows.map(row => { - return ( - - {row.getVisibleCells().map(cell => ( - - ))} - - ) - })} - -
{ - const thisCol = table.getColumn(id)! //avoid serialization error - thisCol.toggleSorting() - })} - colSpan={header.colSpan} - > - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} -
- {flexRender(cell.column.columnDef.cell, cell.getContext())} -
- -
-
-
- {Object.keys(rowSelection).length} of{' '} - {table.getPreFilteredRowModel().rows.length} Total Rows Selected -
-
-
-
- -
-
- -
{JSON.stringify(table.getState().rowSelection, null, 2)}
-
-
- ) -}) - -const IndeterminateCheckbox = component$<{ - indeterminate?: boolean -}>(({ indeterminate, ...rest }) => { - const inputSig = useSignal() - - return -}) - -render(document.getElementById('app') as HTMLElement, ) diff --git a/examples/qwik/row-selection/tsconfig.json b/examples/qwik/row-selection/tsconfig.json deleted file mode 100644 index 9a262f041f..0000000000 --- a/examples/qwik/row-selection/tsconfig.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "module": "ESNext", - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "jsxImportSource": "@builder.io/qwik", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"], - "references": [{ "path": "./tsconfig.node.json" }] -} diff --git a/examples/qwik/row-selection/tsconfig.node.json b/examples/qwik/row-selection/tsconfig.node.json deleted file mode 100644 index 97ede7ee6f..0000000000 --- a/examples/qwik/row-selection/tsconfig.node.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "composite": true, - "skipLibCheck": true, - "module": "ESNext", - "moduleResolution": "bundler", - "allowSyntheticDefaultImports": true, - "strict": true - }, - "include": ["vite.config.ts"] -} diff --git a/examples/qwik/row-selection/vite.config.ts b/examples/qwik/row-selection/vite.config.ts deleted file mode 100644 index cabd66b013..0000000000 --- a/examples/qwik/row-selection/vite.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { defineConfig } from 'vite' -import { qwikVite } from '@builder.io/qwik/optimizer' - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [ - qwikVite({ - csr: true, - }), - ], -}) diff --git a/examples/qwik/sorting/README.md b/examples/qwik/sorting/README.md deleted file mode 100644 index c813736fcc..0000000000 --- a/examples/qwik/sorting/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# Qwik + Vite - -## Qwik in CSR mode - -This starter is using a pure CSR (Client Side Rendering) mode. This means, that the application is fully bootstrapped in the browser. Most of Qwik innovations however take advantage of SSR (Server Side Rendering) mode. - -```ts -export default defineConfig({ - plugins: [ - qwikVite({ - csr: true, - }), - ], -}) -``` - -Use `npm create qwik@latest` to create a full production ready Qwik application, using SSR and [QwikCity](https://qwik.builder.io/docs/qwikcity/), our server-side metaframwork. - -## Usage - -```bash -$ npm install # or pnpm install or yarn install -``` - -Learn more on the [Qwik Website](https://qwik.builder.io) and join our community on our [Discord](https://qwik.builder.io/chat) - -## Available Scripts - -In the project directory, you can run: - -### `npm run dev` - -Runs the app in the development mode.
-Open [http://localhost:5173](http://localhost:5173) to view it in the browser. - -### `npm run build` - -Builds the app for production to the `dist` folder.
diff --git a/examples/qwik/sorting/index.html b/examples/qwik/sorting/index.html deleted file mode 100644 index 9a3384e789..0000000000 --- a/examples/qwik/sorting/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - Vite + Qwik + TS - - -
- - - diff --git a/examples/qwik/sorting/package.json b/examples/qwik/sorting/package.json deleted file mode 100644 index 290747eeb0..0000000000 --- a/examples/qwik/sorting/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "tanstack-table-example-qwik-sorting", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "serve dist" - }, - "devDependencies": { - "@builder.io/qwik": "^1.6.0", - "@faker-js/faker": "^8.4.1", - "serve": "^14.2.3", - "typescript": "5.4.5", - "vite": "^5.3.2" - }, - "dependencies": { - "@tanstack/qwik-table": "^8.20.5" - } -} diff --git a/examples/qwik/sorting/src/index.css b/examples/qwik/sorting/src/index.css deleted file mode 100644 index 43c09e0f6b..0000000000 --- a/examples/qwik/sorting/src/index.css +++ /dev/null @@ -1,26 +0,0 @@ -html { - font-family: sans-serif; - font-size: 14px; -} - -table { - border: 1px solid lightgray; -} - -tbody { - border-bottom: 1px solid lightgray; -} - -th { - border-bottom: 1px solid lightgray; - border-right: 1px solid lightgray; - padding: 2px 4px; -} - -tfoot { - color: gray; -} - -tfoot th { - font-weight: normal; -} diff --git a/examples/qwik/sorting/src/main.tsx b/examples/qwik/sorting/src/main.tsx deleted file mode 100644 index d1a4d4488f..0000000000 --- a/examples/qwik/sorting/src/main.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import '@builder.io/qwik/qwikloader.js' -import { render, component$, useSignal, $ } from '@builder.io/qwik' - -import './index.css' -import { makeData } from './makeData' - -import { - getCoreRowModel, - flexRender, - useQwikTable, - SortingState, - ColumnDef, - getSortedRowModel, -} from '@tanstack/qwik-table' - -type Person = { - firstName: string - lastName: string - age: number - visits: number - status: string - progress: number -} - -const columns: ColumnDef[] = [ - { - accessorKey: 'firstName', - cell: info => info.getValue(), - footer: props => props.column.id, - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - cell: info => info.getValue(), - header: () => Last Name, - footer: props => props.column.id, - }, - { - accessorKey: 'age', - header: () => 'Age', - footer: props => props.column.id, - }, - { - accessorKey: 'visits', - header: () => Visits, - footer: props => props.column.id, - }, - { - accessorKey: 'status', - header: 'Status', - footer: props => props.column.id, - }, - { - accessorKey: 'progress', - header: 'Profile Progress', - footer: props => props.column.id, - sortDescFirst: true, // This column will sort in descending order first (default for number columns anyway) - }, - { - accessorKey: 'createdAt', - header: 'Created At', - cell: info => info.getValue().toLocaleDateString(), - // sortingFn: 'datetime' (inferred from the data) - }, -] - -const App = component$(() => { - const data = useSignal(makeData(10_000)) - - const sorting = useSignal([]) - - const table = useQwikTable({ - columns, - data: data.value, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), - enableSorting: true, - state: { - sorting: sorting.value, - }, - onSortingChange: updater => { - sorting.value = - updater instanceof Function ? updater(sorting.value) : updater - }, - }) - - return ( -
- - - {table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(header => { - const { column } = header - const id = column.id - return ( - - ) - })} - - ))} - - - {table - .getRowModel() - .rows.slice(0, 10) - .map(row => { - return ( - - {row.getVisibleCells().map(cell => { - return ( - - ) - })} - - ) - })} - -
- {header.isPlaceholder ? null : ( -
{ - const col = table.getColumn(id)! //avoid serializing errors - col.getToggleSortingHandler()!(event) - })} - title={ - column.getCanSort() - ? column.getNextSortingOrder() === 'asc' - ? 'Sort ascending' - : column.getNextSortingOrder() === 'desc' - ? 'Sort descending' - : 'Clear sort' - : undefined - } - > - {flexRender( - header.column.columnDef.header, - header.getContext() - )} - {{ - asc: ' 🔼', - desc: ' 🔽', - }[header.column.getIsSorted() as string] ?? null} -
- )} -
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} -
-
{table.getRowModel().rows.length.toLocaleString()} Rows
-
{JSON.stringify(sorting, null, 2)}
-
- ) -}) - -render(document.getElementById('app') as HTMLElement, ) diff --git a/examples/qwik/sorting/src/makeData.ts b/examples/qwik/sorting/src/makeData.ts deleted file mode 100644 index a44f798e38..0000000000 --- a/examples/qwik/sorting/src/makeData.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { faker } from '@faker-js/faker' - -export type Person = { - firstName: string - lastName: string - age: number - visits: number - progress: number - status: 'relationship' | 'complicated' | 'single' - createdAt: Date - subRows?: Person[] -} - -const range = (len: number) => { - const arr: number[] = [] - for (let i = 0; i < len; i++) { - arr.push(i) - } - return arr -} - -const newPerson = (): Person => { - return { - firstName: faker.person.firstName(), - lastName: faker.person.lastName(), - age: faker.number.int(40), - visits: faker.number.int(1000), - progress: faker.number.int(100), - createdAt: faker.date.anytime(), - status: faker.helpers.shuffle([ - 'relationship', - 'complicated', - 'single', - ])[0]!, - } -} - -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! - return range(len).map((_d): Person => { - return { - ...newPerson(), - subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, - } - }) - } - - return makeDataLevel() -} diff --git a/examples/qwik/sorting/tsconfig.json b/examples/qwik/sorting/tsconfig.json deleted file mode 100644 index 9a262f041f..0000000000 --- a/examples/qwik/sorting/tsconfig.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "module": "ESNext", - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "jsxImportSource": "@builder.io/qwik", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"], - "references": [{ "path": "./tsconfig.node.json" }] -} diff --git a/examples/qwik/sorting/tsconfig.node.json b/examples/qwik/sorting/tsconfig.node.json deleted file mode 100644 index 97ede7ee6f..0000000000 --- a/examples/qwik/sorting/tsconfig.node.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "composite": true, - "skipLibCheck": true, - "module": "ESNext", - "moduleResolution": "bundler", - "allowSyntheticDefaultImports": true, - "strict": true - }, - "include": ["vite.config.ts"] -} diff --git a/examples/qwik/sorting/vite.config.ts b/examples/qwik/sorting/vite.config.ts deleted file mode 100644 index cabd66b013..0000000000 --- a/examples/qwik/sorting/vite.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { defineConfig } from 'vite' -import { qwikVite } from '@builder.io/qwik/optimizer' - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [ - qwikVite({ - csr: true, - }), - ], -}) diff --git a/examples/react/basic/.gitignore b/examples/react/basic-external-atoms/.gitignore similarity index 100% rename from examples/react/basic/.gitignore rename to examples/react/basic-external-atoms/.gitignore diff --git a/examples/react/basic/README.md b/examples/react/basic-external-atoms/README.md similarity index 100% rename from examples/react/basic/README.md rename to examples/react/basic-external-atoms/README.md diff --git a/examples/react/basic-external-atoms/index.html b/examples/react/basic-external-atoms/index.html new file mode 100644 index 0000000000..1551290f72 --- /dev/null +++ b/examples/react/basic-external-atoms/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/react/basic-external-atoms/package.json b/examples/react/basic-external-atoms/package.json new file mode 100644 index 0000000000..2dbfe66ea3 --- /dev/null +++ b/examples/react/basic-external-atoms/package.json @@ -0,0 +1,29 @@ +{ + "name": "tanstack-react-table-example-basic-external-atoms", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/react-store": "^0.11.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6" + }, + "devDependencies": { + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/react/basic-external-atoms/src/index.css b/examples/react/basic-external-atoms/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/react/basic-external-atoms/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/basic-external-atoms/src/main.tsx b/examples/react/basic-external-atoms/src/main.tsx new file mode 100644 index 0000000000..f22eef25ae --- /dev/null +++ b/examples/react/basic-external-atoms/src/main.tsx @@ -0,0 +1,235 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import './index.css' +import { useCreateAtom, useSelector } from '@tanstack/react-store' +import { + createColumnHelper, + createPaginatedRowModel, + createSortedRowModel, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, + useTable, +} from '@tanstack/react-table' +import { makeData } from './makeData' +import type { PaginationState, SortingState } from '@tanstack/react-table' + +type Person = { + firstName: string + lastName: string + age: number + visits: number + status: string + progress: number +} + +const _features = tableFeatures({ + rowPaginationFeature, + rowSortingFeature, +}) + +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('lastName', { + header: 'Last Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('age', { + header: 'Age', + }), + columnHelper.accessor('visits', { + header: 'Visits', + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + }), +]) + +function App() { + const [data, setData] = React.useState(() => makeData(1_000)) + const refreshData = () => setData(makeData(1_000)) + const stressTest = () => setData(makeData(200_000)) + + const rerender = React.useReducer(() => ({}), {})[1] + + // Create stable external atoms for the individual state slices you want to + // own. The table still creates internal base atoms for everything else. + const sortingAtom = useCreateAtom([]) + const paginationAtom = useCreateAtom({ + pageIndex: 0, + pageSize: 10, + }) + + // Subscribe to each atom independently — fine-grained reactivity. + const sorting = useSelector(sortingAtom) + const pagination = useSelector(paginationAtom) + + console.log('sorting', sorting) + console.log('pagination', pagination) + + // Create the table and pass your per-slice external atoms. + const table = useTable( + { + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data, + atoms: { + sorting: sortingAtom, + pagination: paginationAtom, + }, + debugTable: true, + }, + (state) => state, // default selector + ) + + return ( +
+
+ + +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getAllCells().map((cell) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( +
+ + {{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null} +
+ )} +
+ +
+
+
+ + + + + +
Page
+ + {(table.atoms.pagination.get().pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = e.target.value ? Number(e.target.value) - 1 : 0 + table.setPageIndex(page) + }} + className="page-size-input" + /> + + +
+
+ +
+        {JSON.stringify(
+          {
+            sorting: table.atoms.sorting.get(),
+            pagination: table.atoms.pagination.get(),
+          },
+          null,
+          2,
+        )}
+      
+
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +ReactDOM.createRoot(rootElement).render( + + + , +) diff --git a/examples/react/basic-external-atoms/src/makeData.ts b/examples/react/basic-external-atoms/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/react/basic-external-atoms/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/react/basic-external-atoms/src/vite-env.d.ts b/examples/react/basic-external-atoms/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/basic-external-atoms/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/basic-external-atoms/tsconfig.json b/examples/react/basic-external-atoms/tsconfig.json new file mode 100644 index 0000000000..33a3e872be --- /dev/null +++ b/examples/react/basic-external-atoms/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/react/basic-external-atoms/vite.config.js b/examples/react/basic-external-atoms/vite.config.js new file mode 100644 index 0000000000..1755f6bea8 --- /dev/null +++ b/examples/react/basic-external-atoms/vite.config.js @@ -0,0 +1,23 @@ +import { defineConfig } from 'vite' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), + ], +}) diff --git a/examples/react/bootstrap/.gitignore b/examples/react/basic-external-state/.gitignore similarity index 100% rename from examples/react/bootstrap/.gitignore rename to examples/react/basic-external-state/.gitignore diff --git a/examples/react/bootstrap/README.md b/examples/react/basic-external-state/README.md similarity index 100% rename from examples/react/bootstrap/README.md rename to examples/react/basic-external-state/README.md diff --git a/examples/react/basic-external-state/index.html b/examples/react/basic-external-state/index.html new file mode 100644 index 0000000000..1551290f72 --- /dev/null +++ b/examples/react/basic-external-state/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/react/basic-external-state/package.json b/examples/react/basic-external-state/package.json new file mode 100644 index 0000000000..fdfe48a076 --- /dev/null +++ b/examples/react/basic-external-state/package.json @@ -0,0 +1,29 @@ +{ + "name": "tanstack-react-table-example-basic-external-state", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/react-store": "^0.11.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6" + }, + "devDependencies": { + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/react/basic-external-state/src/index.css b/examples/react/basic-external-state/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/react/basic-external-state/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/basic-external-state/src/main.tsx b/examples/react/basic-external-state/src/main.tsx new file mode 100644 index 0000000000..031c3f290b --- /dev/null +++ b/examples/react/basic-external-state/src/main.tsx @@ -0,0 +1,224 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import './index.css' +import { + createColumnHelper, + createPaginatedRowModel, + createSortedRowModel, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, + useTable, +} from '@tanstack/react-table' +import { makeData } from './makeData' +import type { PaginationState, SortingState } from '@tanstack/react-table' + +type Person = { + firstName: string + lastName: string + age: number + visits: number + status: string + progress: number +} + +const _features = tableFeatures({ + rowPaginationFeature, + rowSortingFeature, +}) + +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('lastName', { + header: 'Last Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('age', { + header: 'Age', + }), + columnHelper.accessor('visits', { + header: 'Visits', + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + }), +]) + +function App() { + const [data, setData] = React.useState(() => makeData(1_000)) + const refreshData = () => setData(makeData(1_000)) + const stressTest = () => setData(makeData(200_000)) + + const rerender = React.useReducer(() => ({}), {})[1] + + // Manage sorting state with React.useState (although react state causes more re-renders here than necessary compared to using a store) + const [sorting, setSorting] = React.useState([]) + + // Manage pagination state with React.useState (although react state causes more re-renders here than necessary compared to using a store) + const [pagination, setPagination] = React.useState({ + pageIndex: 0, + pageSize: 10, + }) + + // console.log('sorting', sorting) + // console.log('pagination', pagination) + + // Create the table and pass state + onChange handlers + const table = useTable( + { + debugTable: true, + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data, + state: { + sorting, // connect our sorting state back down to the table + pagination, // connect our pagination state back down to the table + }, + onSortingChange: setSorting, // raise sorting state changes to our own state management + onPaginationChange: setPagination, // raise pagination state changes to our own state management + }, + (state) => state, // default selector + ) + + return ( +
+
+ + +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getAllCells().map((cell) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( +
+ + {{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null} +
+ )} +
+ +
+
+
+ + + + + +
Page
+ + {(pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = e.target.value ? Number(e.target.value) - 1 : 0 + table.setPageIndex(page) + }} + className="page-size-input" + /> + + +
+
+ +
{JSON.stringify({ sorting, pagination }, null, 2)}
+
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +ReactDOM.createRoot(rootElement).render( + + + , +) diff --git a/examples/react/basic-external-state/src/makeData.ts b/examples/react/basic-external-state/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/react/basic-external-state/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/react/basic-external-state/src/vite-env.d.ts b/examples/react/basic-external-state/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/basic-external-state/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/basic-external-state/tsconfig.json b/examples/react/basic-external-state/tsconfig.json new file mode 100644 index 0000000000..33a3e872be --- /dev/null +++ b/examples/react/basic-external-state/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/react/basic-external-state/vite.config.js b/examples/react/basic-external-state/vite.config.js new file mode 100644 index 0000000000..1755f6bea8 --- /dev/null +++ b/examples/react/basic-external-state/vite.config.js @@ -0,0 +1,23 @@ +import { defineConfig } from 'vite' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), + ], +}) diff --git a/examples/react/custom-features/.gitignore b/examples/react/basic-subscribe/.gitignore similarity index 100% rename from examples/react/custom-features/.gitignore rename to examples/react/basic-subscribe/.gitignore diff --git a/examples/react/basic-subscribe/README.md b/examples/react/basic-subscribe/README.md new file mode 100644 index 0000000000..89dfe13876 --- /dev/null +++ b/examples/react/basic-subscribe/README.md @@ -0,0 +1,6 @@ +# Basic Subscribe Example + +To run this example: + +- `pnpm install` +- `pnpm start` diff --git a/examples/react/basic-subscribe/index.html b/examples/react/basic-subscribe/index.html new file mode 100644 index 0000000000..1551290f72 --- /dev/null +++ b/examples/react/basic-subscribe/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/react/basic-subscribe/package.json b/examples/react/basic-subscribe/package.json new file mode 100644 index 0000000000..0ab2005b81 --- /dev/null +++ b/examples/react/basic-subscribe/package.json @@ -0,0 +1,32 @@ +{ + "name": "tanstack-react-table-example-basic-subscribe", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/react-pacer": "^0.22.0", + "@tanstack/react-store": "^0.11.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6" + }, + "devDependencies": { + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@tanstack/react-devtools": "^0.10.2", + "@tanstack/react-table-devtools": "^9.0.0-alpha.43", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/react/basic-subscribe/src/index.css b/examples/react/basic-subscribe/src/index.css new file mode 100644 index 0000000000..0a099f6d9f --- /dev/null +++ b/examples/react/basic-subscribe/src/index.css @@ -0,0 +1,372 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +button:disabled, +button[disabled] { + opacity: 0.5; + cursor: not-allowed; + pointer-events: none; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/basic-subscribe/src/main.tsx b/examples/react/basic-subscribe/src/main.tsx new file mode 100644 index 0000000000..f116c0a215 --- /dev/null +++ b/examples/react/basic-subscribe/src/main.tsx @@ -0,0 +1,487 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import { + Subscribe, + columnFilteringFeature, + createColumnHelper, + createFilteredRowModel, + createPaginatedRowModel, + filterFns, + globalFilteringFeature, + rowPaginationFeature, + rowSelectionFeature, + tableFeatures, + useTable, +} from '@tanstack/react-table' +import { useDebouncedCallback } from '@tanstack/react-pacer/debouncer' +import { + tableDevtoolsPlugin, + useTanStackTableDevtools, +} from '@tanstack/react-table-devtools' +import { TanStackDevtools } from '@tanstack/react-devtools' +import { useCreateAtom } from '@tanstack/react-store' +import { makeData } from './makeData' +import type { HTMLProps } from 'react' +import type { Person } from './makeData' +import type { + Column, + ReactTable, + RowSelectionState, +} from '@tanstack/react-table' +import './index.css' + +const _features = tableFeatures({ + rowPaginationFeature, + rowSelectionFeature, + columnFilteringFeature, + globalFilteringFeature, +}) + +const columnHelper = createColumnHelper() + +/** + * This is an example showing how to use advanced re-rendering optimizations with more fine-grained control over what is subscribed to. + * Subscribe/table.Subscribe is a higher-order component that allows you to subscribe to the table state or individual atoms/stores. + * This is useful for making sure that re-renders only happen at certain parts of the react tree exactly where need to be. + * We recommend only using these patterns when you run into specific performance issues. + */ +function App() { + const rerender = React.useReducer(() => ({}), {})[1] + + const columns = React.useMemo( + () => + columnHelper.columns([ + columnHelper.display({ + id: 'select', + header: ({ table }) => { + return ( + // just import Subscribe component if "react" table is not available in scope and pass in the table store as source + ({ + columnFilters: state.columnFilters, + globalFilter: state.globalFilter, + rowSelection: state.rowSelection, + })} + > + {() => ( + + )} + + ) + }, + cell: ({ row }) => ( + rowSelection[row.id]} // optimize to only re-render when the row selection changes for this row + > + {(isRowSelected) => ( +
+ {/* Select only this row's selection value so toggling one row only re-renders that row's checkbox. */} + +
+ )} +
+ ), + }), + columnHelper.accessor('firstName', { + header: 'First Name', + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + header: () => Last Name, + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + }), + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), + ]), + [], + ) + + const [data, setData] = React.useState(() => makeData(1_000)) + const refreshData = () => setData(makeData(1_000)) + const stressTest = () => setData(makeData(200_000)) + + // optionally, raise the selection state to your own atom + const rowSelectionAtom = useCreateAtom({}) + + const table = useTable( + { + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + }, + atoms: { + rowSelection: rowSelectionAtom, + }, + columns, + data, + getRowId: (row) => row.id, + enableRowSelection: true, // enable row selection for all rows + // enableRowSelection: row => row.original.age > 18, // or enable row selection conditionally per row + debugTable: true, + }, + () => null, // subscribe to no table state by default; use table.Subscribe below for targeted updates + ) + + useTanStackTableDevtools(table, 'Basic Subscribe Example') + + return ( +
+
+ + +
+
+ + {(globalFilter) => ( + table.setGlobalFilter(value)} + className="summary-panel" + placeholder="Search all columns..." + /> + )} + +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ) + })} + + ))} + + {/* Subscribe the row model to filtering and pagination only. Row selection is handled per row below. */} + ({ + columnFilters: state.columnFilters, + globalFilter: state.globalFilter, + pagination: state.pagination, + })} + > + {() => ( + <> + + {table.getRowModel().rows.map((row) => { + return ( + + {row.getAllCells().map((cell) => { + return ( + + ) + })} + + ) + })} + + + + + + + + + )} + +
+ {header.isPlaceholder ? null : ( + <> + + {header.column.getCanFilter() ? ( +
+ +
+ ) : null} + + )} +
+ +
+ + {() => ( + + )} + + + Page Rows ( + {table.getRowModel().rows.length.toLocaleString()}) +
+
+ ({ + columnFilters: state.columnFilters, + globalFilter: state.globalFilter, + pagination: state.pagination, + })} + > + {({ pagination }) => ( +
+ + + + + +
Page
+ + {(pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = e.target.value ? Number(e.target.value) - 1 : 0 + table.setPageIndex(page) + }} + className="page-size-input" + /> + + +
+ )} +
+
+ + {(rowSelection) => ( +
+ <>{Object.keys(rowSelection).length.toLocaleString()} of + {table.getPreFilteredRowModel().rows.length.toLocaleString()} Total + Rows Selected +
+ )} +
+
+
+
+ +
+
+ +
+
+ + {/* subscribe to the entire table state */} + state}> + {(state) =>
{JSON.stringify(state, null, 2)}
} +
+
+
+ ) +} + +function Filter({ + column, + table, +}: { + column: Column + table: ReactTable +}) { + const firstValue = table + .getPreFilteredRowModel() + .flatRows[0]?.getValue(column.id) + + return ( + + {() => + typeof firstValue === 'number' ? ( +
+ + column.setFilterValue((old: any) => [value, old?.[1]]) + } + placeholder={`Min`} + className="filter-input" + /> + + column.setFilterValue((old: any) => [old?.[0], value]) + } + placeholder={`Max`} + className="filter-input" + /> +
+ ) : ( + column.setFilterValue(value)} + placeholder={`Search...`} + className="filter-select" + /> + ) + } +
+ ) +} + +// A debounced input react component +function DebouncedInput({ + value: initialValue, + onChange, + debounce = 500, + ...props +}: { + value: string | number + onChange: (value: string | number) => void + debounce?: number +} & Omit, 'onChange'>) { + const [value, setValue] = React.useState(initialValue) + + React.useEffect(() => { + setValue(initialValue) + }, [initialValue]) + + const debouncedOnChange = useDebouncedCallback(onChange, { wait: debounce }) + + return ( + { + setValue(e.target.value) + debouncedOnChange(e.target.value) + }} + /> + ) +} + +function IndeterminateCheckbox({ + indeterminate, + className = '', + ...rest +}: { indeterminate?: boolean } & HTMLProps) { + const ref = React.useRef(null!) + + React.useEffect(() => { + if (typeof indeterminate === 'boolean') { + ref.current.indeterminate = !rest.checked && indeterminate + } + }, [ref, indeterminate]) + + return ( + + ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +ReactDOM.createRoot(rootElement).render( + + + + , +) diff --git a/examples/react/basic-subscribe/src/makeData.ts b/examples/react/basic-subscribe/src/makeData.ts new file mode 100644 index 0000000000..c34c43a03e --- /dev/null +++ b/examples/react/basic-subscribe/src/makeData.ts @@ -0,0 +1,50 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + id: string + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + id: faker.string.uuid(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/react/basic-subscribe/src/vite-env.d.ts b/examples/react/basic-subscribe/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/basic-subscribe/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/basic-subscribe/tsconfig.json b/examples/react/basic-subscribe/tsconfig.json new file mode 100644 index 0000000000..33a3e872be --- /dev/null +++ b/examples/react/basic-subscribe/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/react/basic-subscribe/vite.config.js b/examples/react/basic-subscribe/vite.config.js new file mode 100644 index 0000000000..1755f6bea8 --- /dev/null +++ b/examples/react/basic-subscribe/vite.config.js @@ -0,0 +1,23 @@ +import { defineConfig } from 'vite' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), + ], +}) diff --git a/examples/react/editable-data/.gitignore b/examples/react/basic-use-app-table/.gitignore similarity index 100% rename from examples/react/editable-data/.gitignore rename to examples/react/basic-use-app-table/.gitignore diff --git a/examples/react/custom-features/README.md b/examples/react/basic-use-app-table/README.md similarity index 100% rename from examples/react/custom-features/README.md rename to examples/react/basic-use-app-table/README.md diff --git a/examples/react/basic-use-app-table/index.html b/examples/react/basic-use-app-table/index.html new file mode 100644 index 0000000000..1551290f72 --- /dev/null +++ b/examples/react/basic-use-app-table/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/react/basic-use-app-table/package.json b/examples/react/basic-use-app-table/package.json new file mode 100644 index 0000000000..023b75197b --- /dev/null +++ b/examples/react/basic-use-app-table/package.json @@ -0,0 +1,27 @@ +{ + "name": "tanstack-react-table-example-basic-use-app-table", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6" + }, + "devDependencies": { + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/react/basic-use-app-table/src/index.css b/examples/react/basic-use-app-table/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/react/basic-use-app-table/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/basic-use-app-table/src/main.tsx b/examples/react/basic-use-app-table/src/main.tsx new file mode 100644 index 0000000000..e00d6c98eb --- /dev/null +++ b/examples/react/basic-use-app-table/src/main.tsx @@ -0,0 +1,173 @@ +import * as React from 'react' +import ReactDOM from 'react-dom/client' +import { createTableHook } from '@tanstack/react-table' +import './index.css' + +// This example uses the new `createTableHook` method to create a re-usable table hook factory instead of independently using the standalone `useTable` hook and `createColumnHelper` method. You can choose to use either way. + +// 1. Define what the shape of your data will be for each row +type Person = { + firstName: string + lastName: string + age: number + visits: number + status: string + progress: number +} + +// 2. Create some dummy data with a stable reference (this could be an API response stored in useState or similar) +const defaultData: Array = [ + { + firstName: 'tanner', + lastName: 'linsley', + age: 24, + visits: 100, + status: 'In Relationship', + progress: 50, + }, + { + firstName: 'tandy', + lastName: 'miller', + age: 40, + visits: 40, + status: 'Single', + progress: 80, + }, + { + firstName: 'joe', + lastName: 'dirte', + age: 45, + visits: 20, + status: 'Complicated', + progress: 10, + }, + { + firstName: 'kevin', + lastName: 'vandy', + age: 28, + visits: 100, + status: 'Single', + progress: 70, + }, +] + +// 3. New in V9! Tell the table which features and row models we want to use. In this case, this will be a basic table with no additional features +const { useAppTable, createAppColumnHelper } = createTableHook({ + _features: {}, + _rowModels: {}, // client-side row models. `Core` row model is now included by default, but you can still override it here + debugTable: true, +}) + +// 4. Create a helper object to help define our columns +const columnHelper = createAppColumnHelper() + +// 5. Define the columns for your table with a stable reference (in this case, defined statically outside of a react component) +const columns = columnHelper.columns([ + // accessorKey method (most common for simple use-cases) + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (info) => info.column.id, + }), + // accessorFn used (alternative) along with a custom id + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => {info.getValue()}, + header: () => Last Name, + footer: (info) => info.column.id, + }), + // accessorFn used to transform the data + columnHelper.accessor((row) => Number(row.age), { + id: 'age', + header: () => 'Age', + cell: (info) => info.renderValue(), + footer: (info) => info.column.id, + }), + columnHelper.accessor('visits', { + header: () => Visits, + footer: (info) => info.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (info) => info.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (info) => info.column.id, + }), +]) + +function App() { + // 6. Store data with a stable reference + const [data, _setData] = React.useState(() => [...defaultData]) + const rerender = React.useReducer(() => ({}), {})[1] + + // 7. Create the table instance with the required columns and data. + // Features and row models are already defined in the createTableHook call above + const table = useAppTable( + { + debugTable: true, + columns, + data, + // add additional table options here or in the createTableHook call above + }, + (state) => state, // default selector + ) + + // 8. Render your table markup from the table instance APIs + return ( +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getAllCells().map((cell) => ( + + ))} + + ))} + + + {table.getFooterGroups().map((footerGroup) => ( + + {footerGroup.headers.map((header) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( + + )} +
+ +
+ {header.isPlaceholder ? null : ( + + )} +
+
+ +
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +ReactDOM.createRoot(rootElement).render( + + + , +) diff --git a/examples/react/basic-use-app-table/src/vite-env.d.ts b/examples/react/basic-use-app-table/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/basic-use-app-table/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/basic-use-app-table/tsconfig.json b/examples/react/basic-use-app-table/tsconfig.json new file mode 100644 index 0000000000..33a3e872be --- /dev/null +++ b/examples/react/basic-use-app-table/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/react/basic-use-app-table/vite.config.js b/examples/react/basic-use-app-table/vite.config.js new file mode 100644 index 0000000000..1755f6bea8 --- /dev/null +++ b/examples/react/basic-use-app-table/vite.config.js @@ -0,0 +1,23 @@ +import { defineConfig } from 'vite' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), + ], +}) diff --git a/examples/react/basic-use-legacy-table/index.html b/examples/react/basic-use-legacy-table/index.html new file mode 100644 index 0000000000..9d1db7a15d --- /dev/null +++ b/examples/react/basic-use-legacy-table/index.html @@ -0,0 +1,13 @@ + + + + + + TanStack Table - useLegacyTable Example + + + +
+ + + diff --git a/examples/react/basic-use-legacy-table/package.json b/examples/react/basic-use-legacy-table/package.json new file mode 100644 index 0000000000..d9e03dceed --- /dev/null +++ b/examples/react/basic-use-legacy-table/package.json @@ -0,0 +1,29 @@ +{ + "name": "tanstack-react-table-example-basic-use-legacy-table", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/react-pacer": "^0.22.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6" + }, + "devDependencies": { + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/react/basic-use-legacy-table/src/index.css b/examples/react/basic-use-legacy-table/src/index.css new file mode 100644 index 0000000000..c6c15154a9 --- /dev/null +++ b/examples/react/basic-use-legacy-table/src/index.css @@ -0,0 +1,373 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} + +.state-section { + margin-top: 1rem; +} + +.state-heading { + font-weight: 700; +} diff --git a/examples/react/basic-use-legacy-table/src/main.tsx b/examples/react/basic-use-legacy-table/src/main.tsx new file mode 100644 index 0000000000..c01b16e12b --- /dev/null +++ b/examples/react/basic-use-legacy-table/src/main.tsx @@ -0,0 +1,381 @@ +/** + * This example demonstrates the useLegacyTable hook which provides + * a v8-style API for easier migration from TanStack Table v8 to v9. + * + * Key differences from the v9 useTable hook: + * - No need to define _features - all stock features are included + * - Uses v8-style get*RowModel() options instead of _rowModels + * - Subscribes to all state automatically (like v8 behavior) + * + * NOTE: useLegacyTable is deprecated and intended only as a migration aid. + * New code should use useTable with explicit _features and _rowModels. + */ +import React from 'react' +import ReactDOM from 'react-dom/client' +import './index.css' +import { flexRender } from '@tanstack/react-table' +import { useDebouncedCallback } from '@tanstack/react-pacer/debouncer' +import { + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + legacyCreateColumnHelper, + useLegacyTable, +} from '@tanstack/react-table/legacy' +import { makeData } from './makeData' +import type { + CellData, + ColumnFiltersState, + PaginationState, + RowData, + SortingState, + TableFeatures, +} from '@tanstack/react-table' +import type { LegacyColumn } from '@tanstack/react-table/legacy' // legacy types +import type { Person } from './makeData' + +declare module '@tanstack/react-table' { + // allows us to define custom properties for our columns + interface ColumnMeta< + TFeatures extends TableFeatures, + TData extends RowData, + TValue extends CellData = CellData, + > { + filterVariant?: 'text' | 'range' | 'select' + } +} + +const columnHelper = legacyCreateColumnHelper() + +function App() { + const rerender = React.useReducer(() => ({}), {})[1] + + const columns = React.useMemo( + () => + columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + }), + columnHelper.accessor((row) => `${row.firstName} ${row.lastName}`, { + id: 'fullName', + header: 'Full Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('age', { + header: () => 'Age', + meta: { + filterVariant: 'range', + }, + }), + columnHelper.accessor('visits', { + header: () => Visits, + meta: { + filterVariant: 'range', + }, + }), + columnHelper.accessor('status', { + header: 'Status', + meta: { + filterVariant: 'select', + }, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + meta: { + filterVariant: 'range', + }, + }), + ]), + [], + ) + + const [data, setData] = React.useState>(() => makeData(5_000)) + const refreshData = () => setData((_old) => makeData(50_000)) // stress test + + const [sorting, setSorting] = React.useState([]) + const [columnFilters, setColumnFilters] = React.useState( + [], + ) + const [pagination, setPagination] = React.useState({ + pageIndex: 0, + pageSize: 10, + }) + + // Using useLegacyTable with the v8-style API! + // Notice how we use get*RowModel() options instead of _rowModels + // and we don't need to define _features + const table = useLegacyTable({ + columns, + data, + // V8-style row model options (these are mapped to v9 _rowModels under the hood) + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), // client side filtering + getSortedRowModel: getSortedRowModel(), // client side sorting + getPaginationRowModel: getPaginationRowModel(), + state: { + columnFilters, + pagination, + sorting, + }, + onColumnFiltersChange: setColumnFilters, + onPaginationChange: setPagination, + onSortingChange: setSorting, + // Debug options work the same + debugTable: true, + debugColumns: true, + }) + + return ( +
+
+ Migration Example: This example uses the deprecated{' '} + useLegacyTable hook with v8-style API. See the{' '} + + filters example + {' '} + for the recommended v9 approach. +
+ + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ) + })} + + ))} + + + {table.getRowModel().rows.map((row) => { + return ( + + {row.getAllCells().map((cell) => { + return ( + + ) + })} + + ) + })} + +
+ {header.isPlaceholder ? null : ( + <> +
+ {/* With useLegacyTable, we use flexRender instead of table.FlexRender */} + {flexRender( + header.column.columnDef.header, + header.getContext(), + )} + {{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null} +
+ {header.column.getCanFilter() ? ( +
+ +
+ ) : null} + + )} +
+ {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} +
+
+
+ + + + + +
Page
+ + {(pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = e.target.value ? Number(e.target.value) - 1 : 0 + table.setPageIndex(page) + }} + className="page-size-input" + /> + + +
+
+ {table.getPrePaginatedRowModel().rows.length.toLocaleString()} Rows +
+
+ +
+
+ +
+
+

Current State:

+
+          {JSON.stringify(table.getState(), null, 2)}
+        
+
+
+ ) +} + +function Filter({ column }: { column: LegacyColumn }) { + const columnFilterValue = column.getFilterValue() + const { filterVariant } = column.columnDef.meta ?? {} + + return filterVariant === 'range' ? ( +
+
+ {/* See faceted column filters example for min max values functionality */} + + column.setFilterValue((old: [number, number] | undefined) => [ + value, + old?.[1], + ]) + } + placeholder={`Min`} + className="filter-input" + /> + + column.setFilterValue((old: [number, number] | undefined) => [ + old?.[0], + value, + ]) + } + placeholder={`Max`} + className="filter-input" + /> +
+
+
+ ) : filterVariant === 'select' ? ( + + ) : ( + column.setFilterValue(value)} + placeholder={`Search...`} + type="text" + value={(columnFilterValue ?? '') as string} + /> + // See faceted column filters example for datalist search suggestions + ) +} + +// A typical debounced input react component +function DebouncedInput({ + value: initialValue, + onChange, + debounce = 500, + ...props +}: { + value: string | number + onChange: (value: string | number) => void + debounce?: number +} & Omit, 'onChange'>) { + const [value, setValue] = React.useState(initialValue) + + React.useEffect(() => { + setValue(initialValue) + }, [initialValue]) + + const debouncedOnChange = useDebouncedCallback(onChange, { wait: debounce }) + + return ( + { + setValue(e.target.value) + debouncedOnChange(e.target.value) + }} + /> + ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +ReactDOM.createRoot(rootElement).render( + + + , +) diff --git a/examples/react/basic-use-legacy-table/src/makeData.ts b/examples/react/basic-use-legacy-table/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/react/basic-use-legacy-table/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/react/basic-use-legacy-table/src/vite-env.d.ts b/examples/react/basic-use-legacy-table/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/basic-use-legacy-table/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/basic-use-legacy-table/tsconfig.json b/examples/react/basic-use-legacy-table/tsconfig.json new file mode 100644 index 0000000000..840883b8ab --- /dev/null +++ b/examples/react/basic-use-legacy-table/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noErrorTruncation": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/react/basic-use-legacy-table/vite.config.js b/examples/react/basic-use-legacy-table/vite.config.js new file mode 100644 index 0000000000..1755f6bea8 --- /dev/null +++ b/examples/react/basic-use-legacy-table/vite.config.js @@ -0,0 +1,23 @@ +import { defineConfig } from 'vite' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), + ], +}) diff --git a/examples/react/fully-controlled/.gitignore b/examples/react/basic-use-table/.gitignore similarity index 100% rename from examples/react/fully-controlled/.gitignore rename to examples/react/basic-use-table/.gitignore diff --git a/examples/react/editable-data/README.md b/examples/react/basic-use-table/README.md similarity index 100% rename from examples/react/editable-data/README.md rename to examples/react/basic-use-table/README.md diff --git a/examples/react/basic-use-table/index.html b/examples/react/basic-use-table/index.html new file mode 100644 index 0000000000..1551290f72 --- /dev/null +++ b/examples/react/basic-use-table/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/react/basic-use-table/package.json b/examples/react/basic-use-table/package.json new file mode 100644 index 0000000000..843fdcc35a --- /dev/null +++ b/examples/react/basic-use-table/package.json @@ -0,0 +1,27 @@ +{ + "name": "tanstack-react-table-example-basic-use-table", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6" + }, + "devDependencies": { + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/react/basic-use-table/src/index.css b/examples/react/basic-use-table/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/react/basic-use-table/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/basic-use-table/src/main.tsx b/examples/react/basic-use-table/src/main.tsx new file mode 100644 index 0000000000..5d60ad4d8f --- /dev/null +++ b/examples/react/basic-use-table/src/main.tsx @@ -0,0 +1,168 @@ +import * as React from 'react' +import ReactDOM from 'react-dom/client' +import { tableFeatures, useTable } from '@tanstack/react-table' +import type { ColumnDef } from '@tanstack/react-table' +import './index.css' + +// This example uses the classic standalone `useTable` hook to create a table without the new `createTableHelper` util. + +// 1. Define what the shape of your data will be for each row +type Person = { + firstName: string + lastName: string + age: number + visits: number + status: string + progress: number +} + +// 2. Create some dummy data with a stable reference (this could be an API response stored in useState or similar) +const defaultData: Array = [ + { + firstName: 'tanner', + lastName: 'linsley', + age: 24, + visits: 100, + status: 'In Relationship', + progress: 50, + }, + { + firstName: 'tandy', + lastName: 'miller', + age: 40, + visits: 40, + status: 'Single', + progress: 80, + }, + { + firstName: 'joe', + lastName: 'dirte', + age: 45, + visits: 20, + status: 'Complicated', + progress: 10, + }, + { + firstName: 'kevin', + lastName: 'vandy', + age: 12, + visits: 100, + status: 'Single', + progress: 70, + }, +] + +// 3. New in V9! Tell the table which features and row models we want to use. In this case, this will be a basic table with no additional features +const _features = tableFeatures({}) // util method to create sharable TFeatures object/type + +// 4. Define the columns for your table. This uses the new `ColumnDef` type to define columns. Alternatively, check out the createTableHelper/createColumnHelper util for an even more type-safe way to define columns. +const columns: Array> = [ + { + accessorKey: 'firstName', // accessorKey method (most common for simple use-cases) + header: 'First Name', + cell: (info) => info.getValue(), + }, + { + accessorFn: (row) => row.lastName, // accessorFn used (alternative) along with a custom id + id: 'lastName', + header: () => Last Name, + cell: (info) => {info.getValue()}, + }, + { + accessorFn: (row) => Number(row.age), // accessorFn used to transform the data + id: 'age', + header: () => 'Age', + cell: (info) => { + return info.renderValue() + }, + }, + { + accessorKey: 'visits', + header: () => Visits, + }, + { + accessorKey: 'status', + header: 'Status', + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + }, +] + +function App() { + // 5. Store data with a stable reference + const [data, _setData] = React.useState(() => [...defaultData]) + const rerender = React.useReducer(() => ({}), {})[1] + + // 6. Create the table instance with required _features, columns, and data + const table = useTable( + { + debugTable: true, + _features, // new required option in V9. Tell the table which features you are importing and using (better tree-shaking) + _rowModels: {}, // `Core` row model is now included by default, but you can still override it here + columns, + data, + // add additional table options here + }, + (state) => state, // default selector + ) + + // 7. Render your table markup from the table instance APIs + return ( +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getAllCells().map((cell) => ( + + ))} + + ))} + + + {table.getFooterGroups().map((footerGroup) => ( + + {footerGroup.headers.map((header) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( + + )} +
+ +
+ {header.isPlaceholder ? null : ( + + )} +
+
+ +
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +ReactDOM.createRoot(rootElement).render( + + + , +) diff --git a/examples/react/basic-use-table/src/vite-env.d.ts b/examples/react/basic-use-table/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/basic-use-table/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/basic-use-table/tsconfig.json b/examples/react/basic-use-table/tsconfig.json new file mode 100644 index 0000000000..33a3e872be --- /dev/null +++ b/examples/react/basic-use-table/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/react/basic-use-table/vite.config.js b/examples/react/basic-use-table/vite.config.js new file mode 100644 index 0000000000..1755f6bea8 --- /dev/null +++ b/examples/react/basic-use-table/vite.config.js @@ -0,0 +1,23 @@ +import { defineConfig } from 'vite' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), + ], +}) diff --git a/examples/react/basic/index.html b/examples/react/basic/index.html deleted file mode 100644 index 3fc40c9367..0000000000 --- a/examples/react/basic/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - Vite App - - - -
- - - diff --git a/examples/react/basic/package.json b/examples/react/basic/package.json deleted file mode 100644 index 8c7c6bd7e0..0000000000 --- a/examples/react/basic/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "tanstack-table-example-basic", - "version": "0.0.0", - "private": true, - "scripts": { - "dev": "vite", - "build": "vite build", - "serve": "vite preview", - "start": "vite" - }, - "dependencies": { - "@tanstack/react-table": "^8.20.6", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "typescript": "5.4.5", - "vite": "^5.3.2" - } -} diff --git a/examples/react/basic/src/index.css b/examples/react/basic/src/index.css deleted file mode 100644 index 43c09e0f6b..0000000000 --- a/examples/react/basic/src/index.css +++ /dev/null @@ -1,26 +0,0 @@ -html { - font-family: sans-serif; - font-size: 14px; -} - -table { - border: 1px solid lightgray; -} - -tbody { - border-bottom: 1px solid lightgray; -} - -th { - border-bottom: 1px solid lightgray; - border-right: 1px solid lightgray; - padding: 2px 4px; -} - -tfoot { - color: gray; -} - -tfoot th { - font-weight: normal; -} diff --git a/examples/react/basic/src/main.tsx b/examples/react/basic/src/main.tsx deleted file mode 100644 index c1f615525e..0000000000 --- a/examples/react/basic/src/main.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import * as React from 'react' -import ReactDOM from 'react-dom/client' - -import './index.css' - -import { - createColumnHelper, - flexRender, - getCoreRowModel, - useReactTable, -} from '@tanstack/react-table' - -type Person = { - firstName: string - lastName: string - age: number - visits: number - status: string - progress: number -} - -const defaultData: Person[] = [ - { - firstName: 'tanner', - lastName: 'linsley', - age: 24, - visits: 100, - status: 'In Relationship', - progress: 50, - }, - { - firstName: 'tandy', - lastName: 'miller', - age: 40, - visits: 40, - status: 'Single', - progress: 80, - }, - { - firstName: 'joe', - lastName: 'dirte', - age: 45, - visits: 20, - status: 'Complicated', - progress: 10, - }, -] - -const columnHelper = createColumnHelper() - -const columns = [ - columnHelper.accessor('firstName', { - cell: info => info.getValue(), - footer: info => info.column.id, - }), - columnHelper.accessor(row => row.lastName, { - id: 'lastName', - cell: info => {info.getValue()}, - header: () => Last Name, - footer: info => info.column.id, - }), - columnHelper.accessor('age', { - header: () => 'Age', - cell: info => info.renderValue(), - footer: info => info.column.id, - }), - columnHelper.accessor('visits', { - header: () => Visits, - footer: info => info.column.id, - }), - columnHelper.accessor('status', { - header: 'Status', - footer: info => info.column.id, - }), - columnHelper.accessor('progress', { - header: 'Profile Progress', - footer: info => info.column.id, - }), -] - -function App() { - const [data, _setData] = React.useState(() => [...defaultData]) - const rerender = React.useReducer(() => ({}), {})[1] - - const table = useReactTable({ - data, - columns, - getCoreRowModel: getCoreRowModel(), - }) - - return ( -
- - - {table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(header => ( - - ))} - - ))} - - - {table.getRowModel().rows.map(row => ( - - {row.getVisibleCells().map(cell => ( - - ))} - - ))} - - - {table.getFooterGroups().map(footerGroup => ( - - {footerGroup.headers.map(header => ( - - ))} - - ))} - -
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} -
- {flexRender(cell.column.columnDef.cell, cell.getContext())} -
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.footer, - header.getContext() - )} -
-
- -
- ) -} - -const rootElement = document.getElementById('root') -if (!rootElement) throw new Error('Failed to find the root element') - -ReactDOM.createRoot(rootElement).render( - - - -) diff --git a/examples/react/basic/tsconfig.json b/examples/react/basic/tsconfig.json deleted file mode 100644 index 6d545f543f..0000000000 --- a/examples/react/basic/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"] -} diff --git a/examples/react/basic/vite.config.js b/examples/react/basic/vite.config.js deleted file mode 100644 index 2e1361723a..0000000000 --- a/examples/react/basic/vite.config.js +++ /dev/null @@ -1,17 +0,0 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' -import rollupReplace from '@rollup/plugin-replace' - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [ - rollupReplace({ - preventAssignment: true, - values: { - __DEV__: JSON.stringify(true), - 'process.env.NODE_ENV': JSON.stringify('development'), - }, - }), - react(), - ], -}) diff --git a/examples/react/bootstrap/index.html b/examples/react/bootstrap/index.html deleted file mode 100644 index 3fc40c9367..0000000000 --- a/examples/react/bootstrap/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - Vite App - - - -
- - - diff --git a/examples/react/bootstrap/package.json b/examples/react/bootstrap/package.json deleted file mode 100644 index 0cc4c1b9a9..0000000000 --- a/examples/react/bootstrap/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "tanstack-table-example-bootstrap", - "version": "0.0.0", - "private": true, - "scripts": { - "dev": "vite", - "build": "vite build", - "serve": "vite preview", - "start": "vite" - }, - "dependencies": { - "@tanstack/react-table": "^8.20.6", - "bootstrap": "^5.3.3", - "react": "^18.3.1", - "react-bootstrap": "2.10.3", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@faker-js/faker": "^8.4.1", - "@rollup/plugin-replace": "^5.0.7", - "@types/bootstrap": "^5.2.10", - "@types/react": "^18.3.3", - "@types/react-bootstrap": "^0.32.36", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "typescript": "5.4.5", - "vite": "^5.3.2" - } -} diff --git a/examples/react/bootstrap/src/index.css b/examples/react/bootstrap/src/index.css deleted file mode 100644 index 43c09e0f6b..0000000000 --- a/examples/react/bootstrap/src/index.css +++ /dev/null @@ -1,26 +0,0 @@ -html { - font-family: sans-serif; - font-size: 14px; -} - -table { - border: 1px solid lightgray; -} - -tbody { - border-bottom: 1px solid lightgray; -} - -th { - border-bottom: 1px solid lightgray; - border-right: 1px solid lightgray; - padding: 2px 4px; -} - -tfoot { - color: gray; -} - -tfoot th { - font-weight: normal; -} diff --git a/examples/react/bootstrap/src/main.tsx b/examples/react/bootstrap/src/main.tsx deleted file mode 100644 index f06911f9b4..0000000000 --- a/examples/react/bootstrap/src/main.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import * as React from 'react' -import ReactDOM from 'react-dom/client' - -import 'bootstrap/dist/css/bootstrap.min.css' - -import { Table as BTable } from 'react-bootstrap' - -import { - ColumnDef, - flexRender, - getCoreRowModel, - useReactTable, -} from '@tanstack/react-table' -import { makeData, Person } from './makeData' - -const columns: ColumnDef[] = [ - { - header: 'Name', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'firstName', - cell: info => info.getValue(), - footer: props => props.column.id, - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - cell: info => info.getValue(), - header: () => Last Name, - footer: props => props.column.id, - }, - ], - }, - { - header: 'Info', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'age', - header: () => 'Age', - footer: props => props.column.id, - }, - { - header: 'More Info', - columns: [ - { - accessorKey: 'visits', - header: () => Visits, - footer: props => props.column.id, - }, - { - accessorKey: 'status', - header: 'Status', - footer: props => props.column.id, - }, - { - accessorKey: 'progress', - header: 'Profile Progress', - footer: props => props.column.id, - }, - ], - }, - ], - }, -] - -function App() { - const [data, setData] = React.useState(makeData(10)) - const rerender = () => setData(makeData(10)) - - const table = useReactTable({ - data, - columns, - getCoreRowModel: getCoreRowModel(), - }) - - return ( -
- - - {table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(header => ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} - - ))} - - ))} - - - {table.getRowModel().rows.map(row => ( - - {row.getVisibleCells().map(cell => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ))} - - ))} - - - {table.getFooterGroups().map(footerGroup => ( - - {footerGroup.headers.map(header => ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.footer, - header.getContext() - )} - - ))} - - ))} - - -
- -
-
- ) -} - -const rootElement = document.getElementById('root') -if (!rootElement) throw new Error('Failed to find the root element') - -ReactDOM.createRoot(rootElement).render( - - - -) diff --git a/examples/react/bootstrap/src/makeData.ts b/examples/react/bootstrap/src/makeData.ts deleted file mode 100644 index aba0c96cd1..0000000000 --- a/examples/react/bootstrap/src/makeData.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { faker } from '@faker-js/faker' - -export type Person = { - firstName: string - lastName: string - age: number - visits: number - progress: number - status: 'relationship' | 'complicated' | 'single' -} - -const range = (len: number) => { - const arr: number[] = [] - for (let i = 0; i < len; i++) { - arr.push(i) - } - return arr -} - -const newPerson = (): Person => { - return { - firstName: faker.person.firstName(), - lastName: faker.person.lastName(), - age: faker.number.int(40), - visits: faker.number.int(1000), - progress: faker.number.int(100), - status: faker.helpers.shuffle([ - 'relationship', - 'complicated', - 'single', - ])[0]!, - } -} - -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! - return range(len).map((d): Person => { - return newPerson() - }) - } - - return makeDataLevel() -} diff --git a/examples/react/bootstrap/tsconfig.json b/examples/react/bootstrap/tsconfig.json deleted file mode 100644 index 6d545f543f..0000000000 --- a/examples/react/bootstrap/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"] -} diff --git a/examples/react/bootstrap/vite.config.js b/examples/react/bootstrap/vite.config.js deleted file mode 100644 index 2e1361723a..0000000000 --- a/examples/react/bootstrap/vite.config.js +++ /dev/null @@ -1,17 +0,0 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' -import rollupReplace from '@rollup/plugin-replace' - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [ - rollupReplace({ - preventAssignment: true, - values: { - __DEV__: JSON.stringify(true), - 'process.env.NODE_ENV': JSON.stringify('development'), - }, - }), - react(), - ], -}) diff --git a/examples/react/column-dnd/index.html b/examples/react/column-dnd/index.html index 3fc40c9367..1551290f72 100644 --- a/examples/react/column-dnd/index.html +++ b/examples/react/column-dnd/index.html @@ -4,7 +4,7 @@ Vite App - +
diff --git a/examples/react/column-dnd/package.json b/examples/react/column-dnd/package.json index bbe1b1e579..ca4682749f 100644 --- a/examples/react/column-dnd/package.json +++ b/examples/react/column-dnd/package.json @@ -1,29 +1,32 @@ { - "name": "tanstack-table-example-column-dnd", - "version": "0.0.0", + "name": "tanstack-react-table-example-column-dnd", "private": true, "scripts": { "dev": "vite", "build": "vite build", - "serve": "vite preview --port 3000", - "start": "vite" + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" }, "dependencies": { - "@dnd-kit/core": "^6.1.0", - "@dnd-kit/modifiers": "^7.0.0", - "@dnd-kit/sortable": "^8.0.0", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", - "@faker-js/faker": "^8.4.1", - "@tanstack/react-table": "^8.20.6", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "@faker-js/faker": "^10.4.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6" }, "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/react/column-dnd/src/index.css b/examples/react/column-dnd/src/index.css index 1bd4bee6e5..b69681d8c9 100644 --- a/examples/react/column-dnd/src/index.css +++ b/examples/react/column-dnd/src/index.css @@ -5,6 +5,8 @@ html { table { border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; } tbody { @@ -45,3 +47,340 @@ tfoot { tfoot th { font-weight: normal; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/column-dnd/src/main.tsx b/examples/react/column-dnd/src/main.tsx index 9365e35ccb..cf28422cdf 100644 --- a/examples/react/column-dnd/src/main.tsx +++ b/examples/react/column-dnd/src/main.tsx @@ -1,49 +1,52 @@ -import React, { CSSProperties } from 'react' +import React from 'react' import ReactDOM from 'react-dom/client' - -import './index.css' - import { - Cell, - ColumnDef, - Header, - flexRender, - getCoreRowModel, - useReactTable, + FlexRender, + columnOrderingFeature, + columnSizingFeature, + createTableHook, } from '@tanstack/react-table' -import { makeData, Person } from './makeData' - -// needed for table body level scope DnD setup import { DndContext, KeyboardSensor, MouseSensor, TouchSensor, closestCenter, - type DragEndEvent, useSensor, useSensors, } from '@dnd-kit/core' import { restrictToHorizontalAxis } from '@dnd-kit/modifiers' import { - arrayMove, SortableContext, + arrayMove, horizontalListSortingStrategy, + useSortable, } from '@dnd-kit/sortable' - -// needed for row & cell level scope DnD setup -import { useSortable } from '@dnd-kit/sortable' import { CSS } from '@dnd-kit/utilities' +import { makeData } from './makeData' +import type { DragEndEvent } from '@dnd-kit/core' +import type { CSSProperties } from 'react' +import type { Person } from './makeData' +import type { Cell, Header } from '@tanstack/react-table' +import './index.css' + +const { appFeatures, useAppTable, createAppColumnHelper } = createTableHook({ + _features: { columnOrderingFeature, columnSizingFeature }, + _rowModels: {}, + debugTable: true, + debugHeaders: true, + debugColumns: true, +}) + +const columnHelper = createAppColumnHelper() const DraggableTableHeader = ({ header, }: { - header: Header + header: Header }) => { const { attributes, isDragging, listeners, setNodeRef, transform } = - useSortable({ - id: header.column.id, - }) + useSortable({ id: header.column.id }) const style: CSSProperties = { opacity: isDragging ? 0.8 : 1, @@ -57,9 +60,7 @@ const DraggableTableHeader = ({ return ( - {header.isPlaceholder - ? null - : flexRender(header.column.columnDef.header, header.getContext())} + {header.isPlaceholder ? null : } @@ -67,7 +68,11 @@ const DraggableTableHeader = ({ ) } -const DragAlongCell = ({ cell }: { cell: Cell }) => { +const DragAlongCell = ({ + cell, +}: { + cell: Cell +}) => { const { isDragging, setNodeRef, transform } = useSortable({ id: cell.column.id, }) @@ -83,83 +88,75 @@ const DragAlongCell = ({ cell }: { cell: Cell }) => { return ( - {flexRender(cell.column.columnDef.cell, cell.getContext())} + ) } function App() { - const columns = React.useMemo[]>( - () => [ - { - accessorKey: 'firstName', - cell: info => info.getValue(), - id: 'firstName', - size: 150, - }, - { - accessorFn: row => row.lastName, - cell: info => info.getValue(), - header: () => Last Name, - id: 'lastName', - size: 150, - }, - { - accessorKey: 'age', - header: () => 'Age', - id: 'age', - size: 120, - }, - { - accessorKey: 'visits', - header: () => Visits, - id: 'visits', - size: 120, - }, - { - accessorKey: 'status', - header: 'Status', - id: 'status', - size: 150, - }, - { - accessorKey: 'progress', - header: 'Profile Progress', - id: 'progress', - size: 180, - }, - ], - [] + const columns = React.useMemo( + () => + columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + id: 'firstName', + size: 150, + }), + columnHelper.accessor((row) => row.lastName, { + cell: (info) => info.getValue(), + header: () => Last Name, + id: 'lastName', + size: 150, + }), + columnHelper.accessor('age', { + header: () => 'Age', + id: 'age', + size: 120, + }), + columnHelper.accessor('visits', { + header: () => Visits, + id: 'visits', + size: 120, + }), + columnHelper.accessor('status', { + header: 'Status', + id: 'status', + size: 150, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + id: 'progress', + size: 180, + }), + ]), + [], ) const [data, setData] = React.useState(() => makeData(20)) - const [columnOrder, setColumnOrder] = React.useState(() => - columns.map(c => c.id!) - ) - const rerender = () => setData(() => makeData(20)) + const refreshData = () => setData(makeData(20)) + const stressTest = () => setData(makeData(1_000)) - const table = useReactTable({ - data, - columns, - getCoreRowModel: getCoreRowModel(), - state: { - columnOrder, + const table = useAppTable( + { + debugTable: true, + columns, + data, + initialState: { + columnOrder: columns.map((c) => c.id!), + }, }, - onColumnOrderChange: setColumnOrder, - debugTable: true, - debugHeaders: true, - debugColumns: true, - }) + (state) => state, // default selector + ) // reorder columns after drag & drop function handleDragEnd(event: DragEndEvent) { const { active, over } = event - if (active && over && active.id !== over.id) { - setColumnOrder(columnOrder => { - const oldIndex = columnOrder.indexOf(active.id as string) - const newIndex = columnOrder.indexOf(over.id as string) - return arrayMove(columnOrder, oldIndex, newIndex) //this is just a splice util + if (over && active.id !== over.id) { + table.setColumnOrder((prevColumnOrder) => { + const oldIndex = prevColumnOrder.indexOf(active.id as string) + const newIndex = prevColumnOrder.indexOf(over.id as string) + return arrayMove(prevColumnOrder, oldIndex, newIndex) // this is just a splice util }) } } @@ -167,7 +164,7 @@ function App() { const sensors = useSensors( useSensor(MouseSensor, {}), useSensor(TouchSensor, {}), - useSensor(KeyboardSensor, {}) + useSensor(KeyboardSensor, {}), ) return ( @@ -178,23 +175,32 @@ function App() { onDragEnd={handleDragEnd} sensors={sensors} > -
-
-
- +
-
+
- {table.getHeaderGroups().map(headerGroup => ( + {table.getHeaderGroups().map((headerGroup) => ( - {headerGroup.headers.map(header => ( + {headerGroup.headers.map((header) => ( ))} @@ -202,12 +208,12 @@ function App() { ))} - {table.getRowModel().rows.map(row => ( + {table.getRowModel().rows.map((row) => ( - {row.getVisibleCells().map(cell => ( + {row.getAllCells().map((cell) => ( @@ -217,7 +223,7 @@ function App() { ))}
-
{JSON.stringify(data, null, 2)}
+
{JSON.stringify(table.state, null, 2)}
) @@ -229,5 +235,5 @@ if (!rootElement) throw new Error('Failed to find the root element') ReactDOM.createRoot(rootElement).render( - + , ) diff --git a/examples/react/column-dnd/src/makeData.ts b/examples/react/column-dnd/src/makeData.ts index 331dd1eb19..b9055b2d8c 100644 --- a/examples/react/column-dnd/src/makeData.ts +++ b/examples/react/column-dnd/src/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,14 +29,14 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! - return range(len).map((d): Person => { +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { return { ...newPerson(), subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, diff --git a/examples/react/column-dnd/src/vite-env.d.ts b/examples/react/column-dnd/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/column-dnd/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/column-dnd/tsconfig.json b/examples/react/column-dnd/tsconfig.json index 6d545f543f..33a3e872be 100644 --- a/examples/react/column-dnd/tsconfig.json +++ b/examples/react/column-dnd/tsconfig.json @@ -5,20 +5,17 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, - "include": ["src"] + "include": ["src", "vite.config.js", "vite.config.ts"] } diff --git a/examples/react/column-dnd/vite.config.js b/examples/react/column-dnd/vite.config.js index 2e1361723a..1755f6bea8 100644 --- a/examples/react/column-dnd/vite.config.js +++ b/examples/react/column-dnd/vite.config.js @@ -1,5 +1,6 @@ import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' import rollupReplace from '@rollup/plugin-replace' // https://vitejs.dev/config/ @@ -13,5 +14,10 @@ export default defineConfig({ }, }), react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), ], }) diff --git a/examples/react/column-groups/index.html b/examples/react/column-groups/index.html index 3fc40c9367..1551290f72 100644 --- a/examples/react/column-groups/index.html +++ b/examples/react/column-groups/index.html @@ -4,7 +4,7 @@ Vite App - +
diff --git a/examples/react/column-groups/package.json b/examples/react/column-groups/package.json index 46c7be166e..5fc9741396 100644 --- a/examples/react/column-groups/package.json +++ b/examples/react/column-groups/package.json @@ -1,24 +1,28 @@ { - "name": "tanstack-table-example-column-groups", - "version": "0.0.0", + "name": "tanstack-react-table-example-column-groups", "private": true, "scripts": { "dev": "vite", "build": "vite build", "serve": "vite preview", - "start": "vite" + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" }, "dependencies": { - "@tanstack/react-table": "^8.20.6", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "@faker-js/faker": "^10.4.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6" }, "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/react/column-groups/src/index.css b/examples/react/column-groups/src/index.css index 43c09e0f6b..b9af6ded32 100644 --- a/examples/react/column-groups/src/index.css +++ b/examples/react/column-groups/src/index.css @@ -5,6 +5,8 @@ html { table { border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; } tbody { @@ -24,3 +26,340 @@ tfoot { tfoot th { font-weight: normal; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/column-groups/src/main.tsx b/examples/react/column-groups/src/main.tsx index d17fbf67e8..b753dfef29 100644 --- a/examples/react/column-groups/src/main.tsx +++ b/examples/react/column-groups/src/main.tsx @@ -1,159 +1,134 @@ import * as React from 'react' import ReactDOM from 'react-dom/client' - import './index.css' - import { createColumnHelper, - flexRender, - getCoreRowModel, - useReactTable, + tableFeatures, + useTable, } from '@tanstack/react-table' +import { makeData } from './makeData' +import type { Person } from './makeData' -type Person = { - firstName: string - lastName: string - age: number - visits: number - status: string - progress: number -} +const _features = tableFeatures({}) -const defaultData: Person[] = [ - { - firstName: 'tanner', - lastName: 'linsley', - age: 24, - visits: 100, - status: 'In Relationship', - progress: 50, - }, - { - firstName: 'tandy', - lastName: 'miller', - age: 40, - visits: 40, - status: 'Single', - progress: 80, - }, - { - firstName: 'joe', - lastName: 'dirte', - age: 45, - visits: 20, - status: 'Complicated', - progress: 10, - }, -] +const columnHelper = createColumnHelper() -const columnHelper = createColumnHelper() - -const columns = [ +// use new columnHelper.columns method to create columns with the same TValue generic so TypeScript doesn't complain when passing columns to useTable +const columns = columnHelper.columns([ columnHelper.group({ id: 'hello', header: () => Hello, - // footer: props => props.column.id, - columns: [ + columns: columnHelper.columns([ columnHelper.accessor('firstName', { - cell: info => info.getValue(), - footer: props => props.column.id, + cell: (info) => info.getValue(), + footer: (props) => props.column.id, }), - columnHelper.accessor(row => row.lastName, { + columnHelper.accessor((row) => row.lastName, { id: 'lastName', - cell: info => info.getValue(), + cell: (info) => info.getValue(), header: () => Last Name, - footer: props => props.column.id, + footer: (props) => props.column.id, }), - ], + ]), }), columnHelper.group({ header: 'Info', - footer: props => props.column.id, - columns: [ + footer: (props) => props.column.id, + columns: columnHelper.columns([ columnHelper.accessor('age', { header: () => 'Age', - footer: props => props.column.id, + footer: (props) => props.column.id, }), columnHelper.group({ header: 'More Info', - columns: [ + columns: columnHelper.columns([ columnHelper.accessor('visits', { header: () => Visits, - footer: props => props.column.id, + footer: (props) => props.column.id, }), columnHelper.accessor('status', { header: 'Status', - footer: props => props.column.id, + footer: (props) => props.column.id, }), columnHelper.accessor('progress', { header: 'Profile Progress', - footer: props => props.column.id, + footer: (props) => props.column.id, }), - ], + ]), }), - ], + ]), }), -] +]) function App() { - const [data, _setData] = React.useState(() => [...defaultData]) + const [data, setData] = React.useState(() => makeData(20)) + const refreshData = () => setData(makeData(20)) + const stressTest = () => setData(makeData(1_000)) const rerender = React.useReducer(() => ({}), {})[1] - const table = useReactTable({ - data, - columns, - getCoreRowModel: getCoreRowModel(), - }) + const table = useTable( + { + debugTable: true, + _features, + _rowModels: {}, + columns, + data, + }, + (state) => state, // default selector + ) return ( -
+
+
+ + +
+
- {table.getHeaderGroups().map(headerGroup => ( + {table.getHeaderGroups().map((headerGroup) => ( - {headerGroup.headers.map(header => ( + {headerGroup.headers.map((header) => ( ))} ))} - {table.getRowModel().rows.map(row => ( + {table.getRowModel().rows.map((row) => ( - {row.getVisibleCells().map(cell => ( + {row.getAllCells().map((cell) => ( ))} ))} - {table.getFooterGroups().map(footerGroup => ( + {table.getFooterGroups().map((footerGroup) => ( - {footerGroup.headers.map(header => ( + {footerGroup.headers.map((header) => ( ))} ))}
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} + {header.isPlaceholder ? null : ( + + )}
- {flexRender(cell.column.columnDef.cell, cell.getContext())} +
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.footer, - header.getContext() - )} + {header.isPlaceholder ? null : ( + + )}
-
-
@@ -166,5 +141,5 @@ if (!rootElement) throw new Error('Failed to find the root element') ReactDOM.createRoot(rootElement).render( - + , ) diff --git a/examples/react/column-groups/src/makeData.ts b/examples/react/column-groups/src/makeData.ts new file mode 100644 index 0000000000..fc070cd5d2 --- /dev/null +++ b/examples/react/column-groups/src/makeData.ts @@ -0,0 +1,52 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string | undefined + age: number + visits: number | undefined + progress: number + status: 'relationship' | 'complicated' | 'single' + rank: number + createdAt: Date + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: Math.random() < 0.1 ? undefined : faker.person.lastName(), + age: faker.number.int(40), + visits: Math.random() < 0.1 ? undefined : faker.number.int(1000), + progress: faker.number.int(100), + createdAt: faker.date.anytime(), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + rank: faker.number.int(100), + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/react/column-groups/src/vite-env.d.ts b/examples/react/column-groups/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/column-groups/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/column-groups/tsconfig.json b/examples/react/column-groups/tsconfig.json index 6d545f543f..33a3e872be 100644 --- a/examples/react/column-groups/tsconfig.json +++ b/examples/react/column-groups/tsconfig.json @@ -5,20 +5,17 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, - "include": ["src"] + "include": ["src", "vite.config.js", "vite.config.ts"] } diff --git a/examples/react/column-groups/vite.config.js b/examples/react/column-groups/vite.config.js index 2e1361723a..1755f6bea8 100644 --- a/examples/react/column-groups/vite.config.js +++ b/examples/react/column-groups/vite.config.js @@ -1,5 +1,6 @@ import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' import rollupReplace from '@rollup/plugin-replace' // https://vitejs.dev/config/ @@ -13,5 +14,10 @@ export default defineConfig({ }, }), react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), ], }) diff --git a/examples/react/column-ordering/index.html b/examples/react/column-ordering/index.html index 3fc40c9367..1551290f72 100644 --- a/examples/react/column-ordering/index.html +++ b/examples/react/column-ordering/index.html @@ -4,7 +4,7 @@ Vite App - +
diff --git a/examples/react/column-ordering/package.json b/examples/react/column-ordering/package.json index 59b5fba55e..acd058058d 100644 --- a/examples/react/column-ordering/package.json +++ b/examples/react/column-ordering/package.json @@ -1,25 +1,28 @@ { - "name": "tanstack-table-example-column-ordering", - "version": "0.0.0", + "name": "tanstack-react-table-example-column-ordering", "private": true, "scripts": { "dev": "vite", "build": "vite build", - "serve": "vite preview --port 3000", - "start": "vite" + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" }, "dependencies": { - "@faker-js/faker": "^8.4.1", - "@tanstack/react-table": "^8.20.6", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "@faker-js/faker": "^10.4.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6" }, "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/react/column-ordering/src/index.css b/examples/react/column-ordering/src/index.css index 93034cdd1b..efd0b26417 100644 --- a/examples/react/column-ordering/src/index.css +++ b/examples/react/column-ordering/src/index.css @@ -5,6 +5,8 @@ html { table { border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; } tbody { @@ -33,3 +35,340 @@ tfoot { tfoot th { font-weight: normal; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/column-ordering/src/main.tsx b/examples/react/column-ordering/src/main.tsx index 470e02d49e..5e7ab7012d 100644 --- a/examples/react/column-ordering/src/main.tsx +++ b/examples/react/column-ordering/src/main.tsx @@ -1,125 +1,117 @@ import React from 'react' import ReactDOM from 'react-dom/client' import { faker } from '@faker-js/faker' - -import './index.css' - import { - ColumnDef, - ColumnOrderState, - flexRender, - getCoreRowModel, - useReactTable, + columnOrderingFeature, + columnVisibilityFeature, + createColumnHelper, + tableFeatures, + useTable, } from '@tanstack/react-table' -import { makeData, Person } from './makeData' +import { makeData } from './makeData' +import type { Person } from './makeData' +import './index.css' + +const _features = tableFeatures({ + columnOrderingFeature, + columnVisibilityFeature, +}) -const defaultColumns: ColumnDef[] = [ - { +const columnHelper = createColumnHelper() + +const defaultColumns = columnHelper.columns([ + columnHelper.group({ header: 'Name', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'firstName', - cell: info => info.getValue(), - footer: props => props.column.id, - }, - { - accessorFn: row => row.lastName, + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { id: 'lastName', - cell: info => info.getValue(), + cell: (info) => info.getValue(), header: () => Last Name, - footer: props => props.column.id, - }, - ], - }, - { + footer: (props) => props.column.id, + }), + ]), + }), + columnHelper.group({ header: 'Info', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'age', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('age', { header: () => 'Age', - footer: props => props.column.id, - }, - { + footer: (props) => props.column.id, + }), + columnHelper.group({ header: 'More Info', - columns: [ - { - accessorKey: 'visits', + columns: columnHelper.columns([ + columnHelper.accessor('visits', { header: () => Visits, - footer: props => props.column.id, - }, - { - accessorKey: 'status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { header: 'Status', - footer: props => props.column.id, - }, - { - accessorKey: 'progress', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { header: 'Profile Progress', - footer: props => props.column.id, - }, - ], - }, - ], - }, -] + footer: (props) => props.column.id, + }), + ]), + }), + ]), + }), +]) function App() { const [data, setData] = React.useState(() => makeData(20)) const [columns] = React.useState(() => [...defaultColumns]) - const [columnVisibility, setColumnVisibility] = React.useState({}) - const [columnOrder, setColumnOrder] = React.useState([]) - - const rerender = () => setData(() => makeData(20)) + const refreshData = () => setData(makeData(20)) + const stressTest = () => setData(makeData(1_000)) - const table = useReactTable({ - data, - columns, - state: { - columnVisibility, - columnOrder, + const table = useTable( + { + _features, + _rowModels: {}, + columns, + data, + debugTable: true, + debugHeaders: true, + debugColumns: true, }, - onColumnVisibilityChange: setColumnVisibility, - onColumnOrderChange: setColumnOrder, - getCoreRowModel: getCoreRowModel(), - debugTable: true, - debugHeaders: true, - debugColumns: true, - }) + (state) => state, // default selector + ) const randomizeColumns = () => { table.setColumnOrder( - faker.helpers.shuffle(table.getAllLeafColumns().map(d => d.id)) + faker.helpers.shuffle(table.getAllLeafColumns().map((d) => d.id)), ) } return ( -
-
-
+
+
+
- {table.getAllLeafColumns().map(column => { + {table.getAllLeafColumns().map((column) => { return ( -
+
@@ -127,62 +119,68 @@ function App() { ) })}
-
-
- + -
-
+
- {table.getHeaderGroups().map(headerGroup => ( + {table.getHeaderGroups().map((headerGroup) => ( - {headerGroup.headers.map(header => ( + {headerGroup.headers.map((header) => ( ))} ))} - {table.getRowModel().rows.map(row => ( + {table.getRowModel().rows.map((row) => ( - {row.getVisibleCells().map(cell => ( + {row.getVisibleCells().map((cell) => ( ))} ))} - {table.getFooterGroups().map(footerGroup => ( + {table.getFooterGroups().map((footerGroup) => ( - {footerGroup.headers.map(header => ( + {footerGroup.headers.map((header) => ( ))} ))}
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} + {header.isPlaceholder ? null : ( + + )}
- {flexRender(cell.column.columnDef.cell, cell.getContext())} +
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.footer, - header.getContext() - )} + {header.isPlaceholder ? null : ( + + )}
-
{JSON.stringify(table.getState().columnOrder, null, 2)}
+
{JSON.stringify(table.state, null, 2)}
) } @@ -193,5 +191,5 @@ if (!rootElement) throw new Error('Failed to find the root element') ReactDOM.createRoot(rootElement).render( - + , ) diff --git a/examples/react/column-ordering/src/makeData.ts b/examples/react/column-ordering/src/makeData.ts index 331dd1eb19..b9055b2d8c 100644 --- a/examples/react/column-ordering/src/makeData.ts +++ b/examples/react/column-ordering/src/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,14 +29,14 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! - return range(len).map((d): Person => { +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { return { ...newPerson(), subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, diff --git a/examples/react/column-ordering/src/vite-env.d.ts b/examples/react/column-ordering/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/column-ordering/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/column-ordering/tsconfig.json b/examples/react/column-ordering/tsconfig.json index 6d545f543f..33a3e872be 100644 --- a/examples/react/column-ordering/tsconfig.json +++ b/examples/react/column-ordering/tsconfig.json @@ -5,20 +5,17 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, - "include": ["src"] + "include": ["src", "vite.config.js", "vite.config.ts"] } diff --git a/examples/react/column-ordering/vite.config.js b/examples/react/column-ordering/vite.config.js index 2e1361723a..1755f6bea8 100644 --- a/examples/react/column-ordering/vite.config.js +++ b/examples/react/column-ordering/vite.config.js @@ -1,5 +1,6 @@ import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' import rollupReplace from '@rollup/plugin-replace' // https://vitejs.dev/config/ @@ -13,5 +14,10 @@ export default defineConfig({ }, }), react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), ], }) diff --git a/examples/react/kitchen-sink/.gitignore b/examples/react/column-pinning-split/.gitignore similarity index 100% rename from examples/react/kitchen-sink/.gitignore rename to examples/react/column-pinning-split/.gitignore diff --git a/examples/react/fully-controlled/README.md b/examples/react/column-pinning-split/README.md similarity index 100% rename from examples/react/fully-controlled/README.md rename to examples/react/column-pinning-split/README.md diff --git a/examples/react/column-pinning-split/index.html b/examples/react/column-pinning-split/index.html new file mode 100644 index 0000000000..1551290f72 --- /dev/null +++ b/examples/react/column-pinning-split/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/react/column-pinning-split/package.json b/examples/react/column-pinning-split/package.json new file mode 100644 index 0000000000..c35f276d92 --- /dev/null +++ b/examples/react/column-pinning-split/package.json @@ -0,0 +1,28 @@ +{ + "name": "tanstack-react-table-example-column-pinning-split", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6" + }, + "devDependencies": { + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/react/column-pinning-split/src/index.css b/examples/react/column-pinning-split/src/index.css new file mode 100644 index 0000000000..efd0b26417 --- /dev/null +++ b/examples/react/column-pinning-split/src/index.css @@ -0,0 +1,374 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td { + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td:last-child { + border-right: 0; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/column-pinning-split/src/main.tsx b/examples/react/column-pinning-split/src/main.tsx new file mode 100644 index 0000000000..dcb16e6e30 --- /dev/null +++ b/examples/react/column-pinning-split/src/main.tsx @@ -0,0 +1,370 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import { faker } from '@faker-js/faker' +import './index.css' +import { + columnOrderingFeature, + columnPinningFeature, + columnVisibilityFeature, + createColumnHelper, + tableFeatures, + useTable, +} from '@tanstack/react-table' +import { makeData } from './makeData' +import type { Person } from './makeData' + +const _features = tableFeatures({ + columnVisibilityFeature, + columnPinningFeature, + columnOrderingFeature, +}) + +const columnHelper = createColumnHelper() +const defaultColumns = columnHelper.columns([ + columnHelper.group({ + header: 'Name', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }), + ]), + }), + columnHelper.group({ + header: 'Info', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + }), + columnHelper.group({ + header: 'More Info', + columns: columnHelper.columns([ + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), + ]), + }), + ]), + }), +]) + +function App() { + const [data, setData] = React.useState(() => makeData(1_000)) + const [columns] = React.useState(() => [...defaultColumns]) + + const refreshData = () => setData(makeData(1_000)) + const stressTest = () => setData(makeData(500_000)) + + const table = useTable( + { + _features, + _rowModels: {}, + columns, + data, + debugTable: true, + debugHeaders: true, + debugColumns: true, + }, + (state) => state, // default selector + ) + + const randomizeColumns = () => { + table.setColumnOrder( + faker.helpers.shuffle(table.getAllLeafColumns().map((d) => d.id)), + ) + } + + return ( +
+
+
+ +
+ {table.getAllLeafColumns().map((column) => { + return ( +
+ +
+ ) + })} +
+
+
+ + + +
+
+

+ This example takes advantage of the "splitting" APIs. (APIs that have + "left, "center", and "right" modifiers) +

+
+ + + {table.getLeftHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table + .getRowModel() + .rows.slice(0, 20) + .map((row) => { + return ( + + {row.getLeftVisibleCells().map((cell) => { + return ( + + ) + })} + + ) + })} + +
+
+ {header.isPlaceholder ? null : ( + + )} +
+ {!header.isPlaceholder && header.column.getCanPin() && ( +
+ {header.column.getIsPinned() !== 'left' ? ( + + ) : null} + {header.column.getIsPinned() ? ( + + ) : null} + {header.column.getIsPinned() !== 'right' ? ( + + ) : null} +
+ )} +
+ +
+ + + {table.getCenterHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table + .getRowModel() + .rows.slice(0, 20) + .map((row) => { + return ( + + {row.getCenterVisibleCells().map((cell) => { + return ( + + ) + })} + + ) + })} + +
+
+ {header.isPlaceholder ? null : ( + + )} +
+ {!header.isPlaceholder && header.column.getCanPin() && ( +
+ {header.column.getIsPinned() !== 'left' ? ( + + ) : null} + {header.column.getIsPinned() ? ( + + ) : null} + {header.column.getIsPinned() !== 'right' ? ( + + ) : null} +
+ )} +
+ +
+ + + {table.getRightHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table + .getRowModel() + .rows.slice(0, 20) + .map((row) => { + return ( + + {row.getRightVisibleCells().map((cell) => { + return ( + + ) + })} + + ) + })} + +
+
+ {header.isPlaceholder ? null : ( + + )} +
+ {!header.isPlaceholder && header.column.getCanPin() && ( +
+ {header.column.getIsPinned() !== 'left' ? ( + + ) : null} + {header.column.getIsPinned() ? ( + + ) : null} + {header.column.getIsPinned() !== 'right' ? ( + + ) : null} +
+ )} +
+ +
+
+
{JSON.stringify(table.state, null, 2)}
+
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +ReactDOM.createRoot(rootElement).render( + + + , +) diff --git a/examples/react/column-pinning-split/src/makeData.ts b/examples/react/column-pinning-split/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/react/column-pinning-split/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/react/column-pinning-split/src/vite-env.d.ts b/examples/react/column-pinning-split/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/column-pinning-split/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/column-pinning-split/tsconfig.json b/examples/react/column-pinning-split/tsconfig.json new file mode 100644 index 0000000000..33a3e872be --- /dev/null +++ b/examples/react/column-pinning-split/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/react/column-pinning-split/vite.config.js b/examples/react/column-pinning-split/vite.config.js new file mode 100644 index 0000000000..1755f6bea8 --- /dev/null +++ b/examples/react/column-pinning-split/vite.config.js @@ -0,0 +1,23 @@ +import { defineConfig } from 'vite' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), + ], +}) diff --git a/examples/react/column-pinning-sticky/index.html b/examples/react/column-pinning-sticky/index.html index 3fc40c9367..1551290f72 100644 --- a/examples/react/column-pinning-sticky/index.html +++ b/examples/react/column-pinning-sticky/index.html @@ -4,7 +4,7 @@ Vite App - +
diff --git a/examples/react/column-pinning-sticky/package.json b/examples/react/column-pinning-sticky/package.json index e6ee9b7995..9d83f490ff 100644 --- a/examples/react/column-pinning-sticky/package.json +++ b/examples/react/column-pinning-sticky/package.json @@ -1,25 +1,28 @@ { - "name": "tanstack-table-example-column-pinning-sticky", - "version": "0.0.0", + "name": "tanstack-react-table-example-column-pinning-sticky", "private": true, "scripts": { "dev": "vite", "build": "vite build", - "serve": "vite preview --port 3000", - "start": "vite" + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" }, "dependencies": { - "@faker-js/faker": "^8.4.1", - "@tanstack/react-table": "^8.20.6", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "@faker-js/faker": "^10.4.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6" }, "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/react/column-pinning-sticky/src/index.css b/examples/react/column-pinning-sticky/src/index.css index 2e804931bd..a6f4017012 100644 --- a/examples/react/column-pinning-sticky/src/index.css +++ b/examples/react/column-pinning-sticky/src/index.css @@ -9,11 +9,13 @@ html { width: 100%; max-width: 960px; position: relative; + border-collapse: collapse; + border-spacing: 0; } table { /* box-shadow and borders will not work with positon: sticky otherwise */ - border-collapse: separate !important; + border-collapse: collapse; border-spacing: 0; } @@ -48,3 +50,340 @@ td { background: blue; opacity: 1; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/column-pinning-sticky/src/main.tsx b/examples/react/column-pinning-sticky/src/main.tsx index 4a554cf39b..d4bea18010 100644 --- a/examples/react/column-pinning-sticky/src/main.tsx +++ b/examples/react/column-pinning-sticky/src/main.tsx @@ -1,22 +1,37 @@ -import React, { CSSProperties } from 'react' +import React from 'react' import ReactDOM from 'react-dom/client' - -import './index.css' - import { - Column, - ColumnDef, - flexRender, - getCoreRowModel, - useReactTable, + columnOrderingFeature, + columnPinningFeature, + columnResizingFeature, + columnSizingFeature, + columnVisibilityFeature, + createColumnHelper, + tableFeatures, + useTable, } from '@tanstack/react-table' -import { makeData, Person } from './makeData' import { faker } from '@faker-js/faker' +import { makeData } from './makeData' +import type { Column } from '@tanstack/react-table' +import type { CSSProperties } from 'react' +import type { Person } from './makeData' +import './index.css' + +const _features = tableFeatures({ + columnOrderingFeature, + columnPinningFeature, + columnResizingFeature, + columnSizingFeature, + columnVisibilityFeature, +}) -//These are the important styles to make sticky column pinning work! -//Apply styles like this using your CSS strategy of choice with this kind of logic to head cells, data cells, footer cells, etc. -//View the index.css file for more needed styles such as border-collapse: separate -const getCommonPinningStyles = (column: Column): CSSProperties => { +const columnHelper = createColumnHelper() +// These are the important styles to make sticky column pinning work! +// Apply styles like this using your CSS strategy of choice with this kind of logic to head cells, data cells, footer cells, etc. +// View the index.css file for more needed styles such as border-collapse: collapse +const getCommonPinningStyles = ( + column: Column, +): CSSProperties => { const isPinned = column.getIsPinned() const isLastLeftPinnedColumn = isPinned === 'left' && column.getIsLastColumn('left') @@ -38,100 +53,95 @@ const getCommonPinningStyles = (column: Column): CSSProperties => { } } -const defaultColumns: ColumnDef[] = [ - { - accessorKey: 'firstName', +const defaultColumns = columnHelper.columns([ + columnHelper.accessor('firstName', { id: 'firstName', header: 'First Name', - cell: info => info.getValue(), - footer: props => props.column.id, + cell: (info) => info.getValue(), + footer: (props) => props.column.id, size: 180, - }, - { - accessorFn: row => row.lastName, + }), + columnHelper.accessor((row) => row.lastName, { id: 'lastName', - cell: info => info.getValue(), + cell: (info) => info.getValue(), header: () => Last Name, - footer: props => props.column.id, + footer: (props) => props.column.id, size: 180, - }, - { - accessorKey: 'age', + }), + columnHelper.accessor('age', { id: 'age', header: 'Age', - footer: props => props.column.id, + footer: (props) => props.column.id, size: 180, - }, - { - accessorKey: 'visits', + }), + columnHelper.accessor('visits', { id: 'visits', header: 'Visits', - footer: props => props.column.id, + footer: (props) => props.column.id, size: 180, - }, - { - accessorKey: 'status', + }), + columnHelper.accessor('status', { id: 'status', header: 'Status', - footer: props => props.column.id, + footer: (props) => props.column.id, size: 180, - }, - { - accessorKey: 'progress', + }), + columnHelper.accessor('progress', { id: 'progress', header: 'Profile Progress', - footer: props => props.column.id, + footer: (props) => props.column.id, size: 180, - }, -] + }), +]) function App() { - const [data, setData] = React.useState(() => makeData(30)) + const [data, setData] = React.useState(() => makeData(20)) const [columns] = React.useState(() => [...defaultColumns]) - const rerender = () => setData(() => makeData(30)) + const refreshData = () => setData(makeData(20)) + const stressTest = () => setData(makeData(1_000)) - const table = useReactTable({ - data, - columns, - getCoreRowModel: getCoreRowModel(), - debugTable: true, - debugHeaders: true, - debugColumns: true, - columnResizeMode: 'onChange', - }) + const table = useTable( + { + _features, + _rowModels: {}, + columns, + data, + debugTable: true, + debugHeaders: true, + debugColumns: true, + columnResizeMode: 'onChange', + }, + (state) => state, // default selector + ) const randomizeColumns = () => { table.setColumnOrder( - faker.helpers.shuffle(table.getAllLeafColumns().map(d => d.id)) + faker.helpers.shuffle(table.getAllLeafColumns().map((d) => d.id)), ) } return ( -
-
-
+
+
+
- {table.getAllLeafColumns().map(column => { + {table.getAllLeafColumns().map((column) => { return ( -
+
@@ -139,16 +149,28 @@ function App() { ) })}
-
-
- + -
-
+
- {table.getHeaderGroups().map(headerGroup => ( + {table.getHeaderGroups().map((headerGroup) => ( - {headerGroup.headers.map(header => { + {headerGroup.headers.map((header) => { const { column } = header return ( ) @@ -229,20 +248,17 @@ function App() { ))} - {table.getRowModel().rows.map(row => ( + {table.getRowModel().rows.map((row) => ( - {row.getVisibleCells().map(cell => { + {row.getVisibleCells().map((cell) => { const { column } = cell return ( ) })} @@ -251,7 +267,7 @@ function App() {
-
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )}{' '} +
+ {header.isPlaceholder ? null : ( + <> + {' '} + + )} {/* Demo getIndex behavior */} {column.getIndex(column.getIsPinned() || 'center')}
{!header.isPlaceholder && header.column.getCanPin() && ( -
+
{header.column.getIsPinned() !== 'left' ? (
)}
header.column.resetSize(), - onMouseDown: header.getResizeHandler(), - onTouchStart: header.getResizeHandler(), - className: `resizer ${ - header.column.getIsResizing() ? 'isResizing' : '' - }`, - }} + onDoubleClick={() => header.column.resetSize()} + onMouseDown={header.getResizeHandler()} + onTouchStart={header.getResizeHandler()} + className={`resizer ${ + header.column.getIsResizing() ? 'isResizing' : '' + }`} />
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} +
-
{JSON.stringify(table.getState().columnPinning, null, 2)}
+
{JSON.stringify(table.state, null, 2)}
) } @@ -262,5 +278,5 @@ if (!rootElement) throw new Error('Failed to find the root element') ReactDOM.createRoot(rootElement).render( - + , ) diff --git a/examples/react/column-pinning-sticky/src/makeData.ts b/examples/react/column-pinning-sticky/src/makeData.ts index 331dd1eb19..b9055b2d8c 100644 --- a/examples/react/column-pinning-sticky/src/makeData.ts +++ b/examples/react/column-pinning-sticky/src/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,14 +29,14 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! - return range(len).map((d): Person => { +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { return { ...newPerson(), subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, diff --git a/examples/react/column-pinning-sticky/src/vite-env.d.ts b/examples/react/column-pinning-sticky/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/column-pinning-sticky/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/column-pinning-sticky/tsconfig.json b/examples/react/column-pinning-sticky/tsconfig.json index 6d545f543f..33a3e872be 100644 --- a/examples/react/column-pinning-sticky/tsconfig.json +++ b/examples/react/column-pinning-sticky/tsconfig.json @@ -5,20 +5,17 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, - "include": ["src"] + "include": ["src", "vite.config.js", "vite.config.ts"] } diff --git a/examples/react/column-pinning-sticky/vite.config.js b/examples/react/column-pinning-sticky/vite.config.js index 2e1361723a..1755f6bea8 100644 --- a/examples/react/column-pinning-sticky/vite.config.js +++ b/examples/react/column-pinning-sticky/vite.config.js @@ -1,5 +1,6 @@ import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' import rollupReplace from '@rollup/plugin-replace' // https://vitejs.dev/config/ @@ -13,5 +14,10 @@ export default defineConfig({ }, }), react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), ], }) diff --git a/examples/react/column-pinning/index.html b/examples/react/column-pinning/index.html index 3fc40c9367..1551290f72 100644 --- a/examples/react/column-pinning/index.html +++ b/examples/react/column-pinning/index.html @@ -4,7 +4,7 @@ Vite App - +
diff --git a/examples/react/column-pinning/package.json b/examples/react/column-pinning/package.json index 34f13c95f3..7e61d45c0c 100644 --- a/examples/react/column-pinning/package.json +++ b/examples/react/column-pinning/package.json @@ -1,25 +1,28 @@ { - "name": "tanstack-table-example-column-pinning", - "version": "0.0.0", + "name": "tanstack-react-table-example-column-pinning", "private": true, "scripts": { "dev": "vite", "build": "vite build", - "serve": "vite preview --port 3000", - "start": "vite" + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" }, "dependencies": { - "@faker-js/faker": "^8.4.1", - "@tanstack/react-table": "^8.20.6", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "@faker-js/faker": "^10.4.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6" }, "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/react/column-pinning/src/index.css b/examples/react/column-pinning/src/index.css index 93034cdd1b..efd0b26417 100644 --- a/examples/react/column-pinning/src/index.css +++ b/examples/react/column-pinning/src/index.css @@ -5,6 +5,8 @@ html { table { border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; } tbody { @@ -33,3 +35,340 @@ tfoot { tfoot th { font-weight: normal; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/column-pinning/src/main.tsx b/examples/react/column-pinning/src/main.tsx index 6c862dc1b2..9c301b9612 100644 --- a/examples/react/column-pinning/src/main.tsx +++ b/examples/react/column-pinning/src/main.tsx @@ -1,131 +1,121 @@ import React from 'react' import ReactDOM from 'react-dom/client' import { faker } from '@faker-js/faker' - import './index.css' - import { - ColumnDef, - ColumnOrderState, - flexRender, - getCoreRowModel, - useReactTable, - VisibilityState, + columnOrderingFeature, + columnPinningFeature, + columnVisibilityFeature, + createTableHook, } from '@tanstack/react-table' -import { makeData, Person } from './makeData' +import { makeData } from './makeData' +import type { Person } from './makeData' + +// Create table hook with features +const { useAppTable, createAppColumnHelper } = createTableHook({ + _features: { + columnVisibilityFeature, + columnPinningFeature, + columnOrderingFeature, + }, + _rowModels: {}, + debugTable: true, + debugHeaders: true, + debugColumns: true, +}) -const defaultColumns: ColumnDef[] = [ - { +// Create column helper +const columnHelper = createAppColumnHelper() + +// Define columns using columnHelper +const columns = columnHelper.columns([ + columnHelper.group({ header: 'Name', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'firstName', - cell: info => info.getValue(), - footer: props => props.column.id, - }, - { - accessorFn: row => row.lastName, + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { id: 'lastName', - cell: info => info.getValue(), + cell: (info) => info.getValue(), header: () => Last Name, - footer: props => props.column.id, - }, - ], - }, - { + footer: (props) => props.column.id, + }), + ]), + }), + columnHelper.group({ header: 'Info', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'age', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('age', { header: () => 'Age', - footer: props => props.column.id, - }, - { + footer: (props) => props.column.id, + }), + columnHelper.group({ header: 'More Info', - columns: [ - { - accessorKey: 'visits', + columns: columnHelper.columns([ + columnHelper.accessor('visits', { header: () => Visits, - footer: props => props.column.id, - }, - { - accessorKey: 'status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { header: 'Status', - footer: props => props.column.id, - }, - { - accessorKey: 'progress', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { header: 'Profile Progress', - footer: props => props.column.id, - }, - ], - }, - ], - }, -] + footer: (props) => props.column.id, + }), + ]), + }), + ]), + }), +]) function App() { - const [data, setData] = React.useState(() => makeData(5000)) - const [columns] = React.useState(() => [...defaultColumns]) - - const [columnVisibility, setColumnVisibility] = - React.useState({}) - const [columnOrder, setColumnOrder] = React.useState([]) - const [columnPinning, setColumnPinning] = React.useState({}) + const [data, setData] = React.useState(() => makeData(1_000)) - const [isSplit, setIsSplit] = React.useState(false) - const rerender = () => setData(() => makeData(5000)) + const refreshData = () => setData(makeData(1_000)) + const stressTest = () => setData(makeData(500_000)) - const table = useReactTable({ - data, - columns, - state: { - columnVisibility, - columnOrder, - columnPinning, + const table = useAppTable( + { + debugTable: true, + columns, + data, }, - onColumnVisibilityChange: setColumnVisibility, - onColumnOrderChange: setColumnOrder, - onColumnPinningChange: setColumnPinning, - getCoreRowModel: getCoreRowModel(), - debugTable: true, - debugHeaders: true, - debugColumns: true, - }) + (state) => state, // default selector + ) const randomizeColumns = () => { table.setColumnOrder( - faker.helpers.shuffle(table.getAllLeafColumns().map(d => d.id)) + faker.helpers.shuffle(table.getAllLeafColumns().map((d) => d.id)), ) } return ( -
-
-
+
+
+
- {table.getAllLeafColumns().map(column => { + {table.getAllLeafColumns().map((column) => { return ( -
+
@@ -133,126 +123,49 @@ function App() { ) })}
-
-
- + -
-
-
- -
-
- {isSplit ? ( - - - {table.getLeftHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(header => ( - - ))} - - ))} - - - {table - .getRowModel() - .rows.slice(0, 20) - .map(row => { - return ( - - {row.getLeftVisibleCells().map(cell => { - return ( - - ) - })} - - ) - })} - -
-
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} -
- {!header.isPlaceholder && header.column.getCanPin() && ( -
- {header.column.getIsPinned() !== 'left' ? ( - - ) : null} - {header.column.getIsPinned() ? ( - - ) : null} - {header.column.getIsPinned() !== 'right' ? ( - - ) : null} -
- )} -
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} -
- ) : null} - +
+

+ This example using the non-split APIs. Columns are just reordered within + 1 table instead of being split into 3 different tables. +

+
+
- {(isSplit - ? table.getCenterHeaderGroups() - : table.getHeaderGroups() - ).map(headerGroup => ( + {table.getHeaderGroups().map((headerGroup) => ( - {headerGroup.headers.map(header => ( + {headerGroup.headers.map((header) => ( - {(isSplit - ? row.getCenterVisibleCells() - : row.getVisibleCells() - ).map(cell => { + {row.getVisibleCells().map((cell) => { return ( ) })} @@ -312,85 +219,8 @@ function App() { })}
-
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} +
+ {header.isPlaceholder ? null : ( + + )}
{!header.isPlaceholder && header.column.getCanPin() && ( -
+
{header.column.getIsPinned() !== 'left' ? (
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} +
- {isSplit ? ( - - - {table.getRightHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(header => ( - - ))} - - ))} - - - {table - .getRowModel() - .rows.slice(0, 20) - .map(row => { - return ( - - {row.getRightVisibleCells().map(cell => { - return ( - - ) - })} - - ) - })} - -
-
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} -
- {!header.isPlaceholder && header.column.getCanPin() && ( -
- {header.column.getIsPinned() !== 'left' ? ( - - ) : null} - {header.column.getIsPinned() ? ( - - ) : null} - {header.column.getIsPinned() !== 'right' ? ( - - ) : null} -
- )} -
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} -
- ) : null}
-
{JSON.stringify(table.getState().columnPinning, null, 2)}
+
{JSON.stringify(table.state, null, 2)}
) } @@ -401,5 +231,5 @@ if (!rootElement) throw new Error('Failed to find the root element') ReactDOM.createRoot(rootElement).render( - + , ) diff --git a/examples/react/column-pinning/src/makeData.ts b/examples/react/column-pinning/src/makeData.ts index 331dd1eb19..b9055b2d8c 100644 --- a/examples/react/column-pinning/src/makeData.ts +++ b/examples/react/column-pinning/src/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,14 +29,14 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! - return range(len).map((d): Person => { +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { return { ...newPerson(), subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, diff --git a/examples/react/column-pinning/src/vite-env.d.ts b/examples/react/column-pinning/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/column-pinning/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/column-pinning/tsconfig.json b/examples/react/column-pinning/tsconfig.json index 6d545f543f..33a3e872be 100644 --- a/examples/react/column-pinning/tsconfig.json +++ b/examples/react/column-pinning/tsconfig.json @@ -5,20 +5,17 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, - "include": ["src"] + "include": ["src", "vite.config.js", "vite.config.ts"] } diff --git a/examples/react/column-pinning/vite.config.js b/examples/react/column-pinning/vite.config.js index 2e1361723a..1755f6bea8 100644 --- a/examples/react/column-pinning/vite.config.js +++ b/examples/react/column-pinning/vite.config.js @@ -1,5 +1,6 @@ import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' import rollupReplace from '@rollup/plugin-replace' // https://vitejs.dev/config/ @@ -13,5 +14,10 @@ export default defineConfig({ }, }), react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), ], }) diff --git a/examples/react/column-resizing-performant/index.html b/examples/react/column-resizing-performant/index.html index 3fc40c9367..1551290f72 100644 --- a/examples/react/column-resizing-performant/index.html +++ b/examples/react/column-resizing-performant/index.html @@ -4,7 +4,7 @@ Vite App - +
diff --git a/examples/react/column-resizing-performant/package.json b/examples/react/column-resizing-performant/package.json index af00cbfa8b..1a24285221 100644 --- a/examples/react/column-resizing-performant/package.json +++ b/examples/react/column-resizing-performant/package.json @@ -1,25 +1,28 @@ { - "name": "tanstack-table-example-column-resizing-performant", - "version": "0.0.0", + "name": "tanstack-react-table-example-column-resizing-performant", "private": true, "scripts": { "dev": "vite", "build": "vite build", - "serve": "vite preview --port 3001", - "start": "vite" + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" }, "dependencies": { - "@faker-js/faker": "^8.4.1", - "@tanstack/react-table": "^8.20.6", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "@faker-js/faker": "^10.4.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6" }, "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/react/column-resizing-performant/src/index.css b/examples/react/column-resizing-performant/src/index.css index f4e2d1501f..d8ee293049 100644 --- a/examples/react/column-resizing-performant/src/index.css +++ b/examples/react/column-resizing-performant/src/index.css @@ -11,6 +11,8 @@ table, .divTable { border: 1px solid lightgray; width: fit-content; + border-collapse: collapse; + border-spacing: 0; } .tr { @@ -71,3 +73,340 @@ td, opacity: 1; } } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/column-resizing-performant/src/main.tsx b/examples/react/column-resizing-performant/src/main.tsx index 089060f209..8d3335ac1a 100644 --- a/examples/react/column-resizing-performant/src/main.tsx +++ b/examples/react/column-resizing-performant/src/main.tsx @@ -1,16 +1,17 @@ import React from 'react' import ReactDOM from 'react-dom/client' - -import './index.css' - import { - useReactTable, - getCoreRowModel, - ColumnDef, - flexRender, - Table, + columnResizingFeature, + columnSizingFeature, + createColumnHelper, + tableFeatures, + useTable, } from '@tanstack/react-table' import { makeData } from './makeData' +import type { Table } from '@tanstack/react-table' +import './index.css' + +const _features = tableFeatures({ columnSizingFeature, columnResizingFeature }) type Person = { firstName: string @@ -21,74 +22,76 @@ type Person = { progress: number } -const defaultColumns: ColumnDef[] = [ - { +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.group({ header: 'Name', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'firstName', - cell: info => info.getValue(), - footer: props => props.column.id, - }, - { - accessorFn: row => row.lastName, + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { id: 'lastName', - cell: info => info.getValue(), + cell: (info) => info.getValue(), header: () => Last Name, - footer: props => props.column.id, - }, - ], - }, - { + footer: (props) => props.column.id, + }), + ]), + }), + columnHelper.group({ header: 'Info', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'age', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('age', { header: () => 'Age', - footer: props => props.column.id, - }, - { - accessorKey: 'visits', + footer: (props) => props.column.id, + }), + columnHelper.accessor('visits', { header: () => Visits, - footer: props => props.column.id, - }, - { - accessorKey: 'status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { header: 'Status', - footer: props => props.column.id, - }, - { - accessorKey: 'progress', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { header: 'Profile Progress', - footer: props => props.column.id, - }, - ], - }, -] + footer: (props) => props.column.id, + }), + ]), + }), +]) function App() { - const [data, _setData] = React.useState(() => makeData(200)) - const [columns] = React.useState(() => [ - ...defaultColumns, - ]) + const [data, setData] = React.useState(() => makeData(200)) + const refreshData = () => setData(makeData(200)) + const stressTest = () => setData(makeData(2_000)) const rerender = React.useReducer(() => ({}), {})[1] - const table = useReactTable({ - data, - columns, - defaultColumn: { - minSize: 60, - maxSize: 800, + const table = useTable( + { + _features, + _rowModels: {}, + columns, + data, + defaultColumn: { + minSize: 60, + maxSize: 800, + }, + columnResizeMode: 'onChange', + debugTable: true, + debugHeaders: true, + debugColumns: true, }, - columnResizeMode: 'onChange', - getCoreRowModel: getCoreRowModel(), - debugTable: true, - debugHeaders: true, - debugColumns: true, - }) + (state) => ({ + columnSizing: state.columnSizing, + columnResizing: state.columnResizing, + }), + ) /** * Instead of calling `column.getSize()` on every render for every header @@ -99,24 +102,32 @@ function App() { const columnSizeVars = React.useMemo(() => { const headers = table.getFlatHeaders() const colSizes: { [key: string]: number } = {} - for (let i = 0; i < headers.length; i++) { - const header = headers[i]! + for (const header of headers) { colSizes[`--header-${header.id}-size`] = header.getSize() colSizes[`--col-${header.column.id}-size`] = header.column.getSize() } return colSizes - }, [table.getState().columnSizingInfo, table.getState().columnSizing]) + }, [table.state.columnResizing, table.state.columnSizing]) - //demo purposes + // demo purposes const [enableMemo, setEnableMemo] = React.useState(true) return ( -
+
+
+ + +
+
This example has artificially slow cell renders to simulate complex usage -
+
-
-
-        {JSON.stringify(
-          {
-            columnSizing: table.getState().columnSizing,
-          },
-          null,
-          2
-        )}
+        {JSON.stringify(table.state, null, 2)}
       
-
({data.length} rows) -
+
({data.length.toLocaleString()} rows) +
{/* Here in the equivalent element (surrounds all table head and data cells), we will define our CSS variables for column sizes */}
element - width: table.getTotalSize(), - }, + className="divTable" + style={{ + ...columnSizeVars, // Define column sizes on the
element + width: table.getTotalSize(), }} >
- {table.getHeaderGroups().map(headerGroup => ( -
- {headerGroup.headers.map(header => ( + {table.getHeaderGroups().map((headerGroup) => ( +
+ {headerGroup.headers.map((header) => (
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} + {header.isPlaceholder ? null : ( + + )}
header.column.resetSize(), - onMouseDown: header.getResizeHandler(), - onTouchStart: header.getResizeHandler(), - className: `resizer ${ - header.column.getIsResizing() ? 'isResizing' : '' - }`, - }} + onDoubleClick={() => header.column.resetSize()} + onMouseDown={header.getResizeHandler()} + onTouchStart={header.getResizeHandler()} + className={`resizer ${ + header.column.getIsResizing() ? 'isResizing' : '' + }`} />
))} @@ -190,7 +181,7 @@ function App() { ))}
{/* When resizing any column we will render this special memoized version of our table body */} - {table.getState().columnSizingInfo.isResizingColumn && enableMemo ? ( + {table.store.state.columnResizing.isResizingColumn && enableMemo ? ( ) : ( @@ -201,35 +192,24 @@ function App() { ) } -//un-memoized normal table body component - see memoized version below -function TableBody({ table }: { table: Table }) { +// un-memoized normal table body component - see memoized version below +function TableBody({ table }: { table: Table }) { return ( -
- {table.getRowModel().rows.map(row => ( -
- {row.getVisibleCells().map(cell => { - //simulate expensive render - for (let i = 0; i < 10000; i++) { +
+ {table.getRowModel().rows.map((row) => ( +
+ {row.getAllCells().map((cell) => { + // simulate expensive render + for (const _ of Array(10000)) { Math.random() } return (
{cell.renderValue()} @@ -242,10 +222,10 @@ function TableBody({ table }: { table: Table }) { ) } -//special memoized wrapper for our table body that we will use during column resizing +// special memoized wrapper for our table body that we will use during column resizing export const MemoizedTableBody = React.memo( TableBody, - (prev, next) => prev.table.options.data === next.table.options.data + (prev, next) => prev.table.options.data === next.table.options.data, ) as typeof TableBody const rootElement = document.getElementById('root') @@ -254,5 +234,5 @@ if (!rootElement) throw new Error('Failed to find the root element') ReactDOM.createRoot(rootElement).render( - + , ) diff --git a/examples/react/column-resizing-performant/src/makeData.ts b/examples/react/column-resizing-performant/src/makeData.ts index 331dd1eb19..b9055b2d8c 100644 --- a/examples/react/column-resizing-performant/src/makeData.ts +++ b/examples/react/column-resizing-performant/src/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,14 +29,14 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! - return range(len).map((d): Person => { +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { return { ...newPerson(), subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, diff --git a/examples/react/column-resizing-performant/src/vite-env.d.ts b/examples/react/column-resizing-performant/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/column-resizing-performant/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/column-resizing-performant/tsconfig.json b/examples/react/column-resizing-performant/tsconfig.json index 6d545f543f..33a3e872be 100644 --- a/examples/react/column-resizing-performant/tsconfig.json +++ b/examples/react/column-resizing-performant/tsconfig.json @@ -5,20 +5,17 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, - "include": ["src"] + "include": ["src", "vite.config.js", "vite.config.ts"] } diff --git a/examples/react/column-resizing-performant/vite.config.js b/examples/react/column-resizing-performant/vite.config.js index 2e1361723a..1755f6bea8 100644 --- a/examples/react/column-resizing-performant/vite.config.js +++ b/examples/react/column-resizing-performant/vite.config.js @@ -1,5 +1,6 @@ import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' import rollupReplace from '@rollup/plugin-replace' // https://vitejs.dev/config/ @@ -13,5 +14,10 @@ export default defineConfig({ }, }), react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), ], }) diff --git a/examples/react/material-ui-pagination/.gitignore b/examples/react/column-resizing/.gitignore similarity index 100% rename from examples/react/material-ui-pagination/.gitignore rename to examples/react/column-resizing/.gitignore diff --git a/examples/react/kitchen-sink/README.md b/examples/react/column-resizing/README.md similarity index 100% rename from examples/react/kitchen-sink/README.md rename to examples/react/column-resizing/README.md diff --git a/examples/react/column-resizing/index.html b/examples/react/column-resizing/index.html new file mode 100644 index 0000000000..1551290f72 --- /dev/null +++ b/examples/react/column-resizing/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/react/column-resizing/package.json b/examples/react/column-resizing/package.json new file mode 100644 index 0000000000..ade82a4ca3 --- /dev/null +++ b/examples/react/column-resizing/package.json @@ -0,0 +1,28 @@ +{ + "name": "tanstack-react-table-example-column-resizing", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6" + }, + "devDependencies": { + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/react/column-resizing/src/index.css b/examples/react/column-resizing/src/index.css new file mode 100644 index 0000000000..d7f648b224 --- /dev/null +++ b/examples/react/column-resizing/src/index.css @@ -0,0 +1,419 @@ +* { + box-sizing: border-box; +} + +html { + font-family: sans-serif; + font-size: 14px; +} + +table, +.divTable { + border: 1px solid lightgray; + width: fit-content; + border-collapse: collapse; + border-spacing: 0; +} + +.tr { + display: flex; +} + +tr, +.tr { + width: fit-content; + height: 30px; +} + +th, +.th, +td, +.td { + box-shadow: inset 0 0 0 1px lightgray; + padding: 0.25rem; +} + +th, +.th { + padding: 2px 4px; + position: relative; + font-weight: bold; + text-align: center; + height: 30px; +} + +td, +.td { + height: 30px; +} + +.resizer { + position: absolute; + top: 0; + height: 100%; + width: 5px; + background: rgba(0, 0, 0, 0.5); + cursor: col-resize; + user-select: none; + touch-action: none; +} + +.resizer.ltr { + right: 0; +} + +.resizer.rtl { + left: 0; +} + +.resizer.isResizing { + background: blue; + opacity: 1; +} + +@media (hover: hover) { + .resizer { + opacity: 0; + } + + *:hover > .resizer { + opacity: 1; + } +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/column-resizing/src/main.tsx b/examples/react/column-resizing/src/main.tsx new file mode 100644 index 0000000000..dfa2822178 --- /dev/null +++ b/examples/react/column-resizing/src/main.tsx @@ -0,0 +1,354 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import { + columnResizingFeature, + columnSizingFeature, + createColumnHelper, + tableFeatures, + useTable, +} from '@tanstack/react-table' +import { makeData } from './makeData' +import type { + ColumnResizeDirection, + ColumnResizeMode, +} from '@tanstack/react-table' +import type { Person } from './makeData' +import './index.css' + +const _features = tableFeatures({ columnResizingFeature, columnSizingFeature }) + +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.group({ + header: 'Name', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }), + ]), + }), + columnHelper.group({ + header: 'Info', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + }), + columnHelper.group({ + header: 'More Info', + columns: columnHelper.columns([ + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), + ]), + }), + ]), + }), +]) + +function App() { + const [data, setData] = React.useState(() => makeData(10)) + const refreshData = () => setData(makeData(10)) + const stressTest = () => setData(makeData(100)) + + const [columnResizeMode, setColumnResizeMode] = + React.useState('onChange') + + const [columnResizeDirection, setColumnResizeDirection] = + React.useState('ltr') + + const rerender = React.useReducer(() => ({}), {})[1] + + const table = useTable( + { + _features, + _rowModels: {}, + columns, + data, + columnResizeMode, + columnResizeDirection, + debugTable: true, + debugHeaders: true, + debugColumns: true, + }, + (state) => state, // default selector + ) + + return ( +
+
+ + +
+
+ + +
+
+
{'
'} +
+
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getAllCells().map((cell) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( + + )} +
header.column.resetSize()} + onMouseDown={header.getResizeHandler()} + onTouchStart={header.getResizeHandler()} + className={`resizer ${ + table.options.columnResizeDirection + } ${header.column.getIsResizing() ? 'isResizing' : ''}`} + style={{ + transform: + columnResizeMode === 'onEnd' && + header.column.getIsResizing() + ? `translateX(${ + (table.options.columnResizeDirection === 'rtl' + ? -1 + : 1) * + (table.store.state.columnResizing + .deltaOffset ?? 0) + }px)` + : '', + }} + /> +
+ +
+
+
+
{'
(relative)'}
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => ( +
+ {headerGroup.headers.map((header) => ( +
+ {header.isPlaceholder ? null : ( + + )} +
header.column.resetSize()} + onMouseDown={header.getResizeHandler()} + onTouchStart={header.getResizeHandler()} + className={`resizer ${ + table.options.columnResizeDirection + } ${header.column.getIsResizing() ? 'isResizing' : ''}`} + style={{ + transform: + columnResizeMode === 'onEnd' && + header.column.getIsResizing() + ? `translateX(${ + (table.options.columnResizeDirection === 'rtl' + ? -1 + : 1) * + (table.store.state.columnResizing + .deltaOffset ?? 0) + }px)` + : '', + }} + /> +
+ ))} +
+ ))} +
+
+ {table.getRowModel().rows.map((row) => ( +
+ {row.getAllCells().map((cell) => ( +
+ +
+ ))} +
+ ))} +
+
+
+
+
{'
(absolute positioning)'}
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => ( +
+ {headerGroup.headers.map((header) => ( +
+ {header.isPlaceholder ? null : ( + + )} +
header.column.resetSize()} + onMouseDown={header.getResizeHandler()} + onTouchStart={header.getResizeHandler()} + className={`resizer ${ + table.options.columnResizeDirection + } ${header.column.getIsResizing() ? 'isResizing' : ''}`} + style={{ + transform: + columnResizeMode === 'onEnd' && + header.column.getIsResizing() + ? `translateX(${ + (table.options.columnResizeDirection === 'rtl' + ? -1 + : 1) * + (table.store.state.columnResizing + .deltaOffset ?? 0) + }px)` + : '', + }} + /> +
+ ))} +
+ ))} +
+
+ {table.getRowModel().rows.map((row) => ( +
+ {row.getAllCells().map((cell) => ( +
+ +
+ ))} +
+ ))} +
+
+
+
+
+ +
{JSON.stringify(table.state, null, 2)}
+
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +ReactDOM.createRoot(rootElement).render( + + + , +) diff --git a/examples/react/column-resizing/src/makeData.ts b/examples/react/column-resizing/src/makeData.ts new file mode 100644 index 0000000000..fc070cd5d2 --- /dev/null +++ b/examples/react/column-resizing/src/makeData.ts @@ -0,0 +1,52 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string | undefined + age: number + visits: number | undefined + progress: number + status: 'relationship' | 'complicated' | 'single' + rank: number + createdAt: Date + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: Math.random() < 0.1 ? undefined : faker.person.lastName(), + age: faker.number.int(40), + visits: Math.random() < 0.1 ? undefined : faker.number.int(1000), + progress: faker.number.int(100), + createdAt: faker.date.anytime(), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + rank: faker.number.int(100), + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/react/column-resizing/src/vite-env.d.ts b/examples/react/column-resizing/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/column-resizing/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/column-resizing/tsconfig.json b/examples/react/column-resizing/tsconfig.json new file mode 100644 index 0000000000..33a3e872be --- /dev/null +++ b/examples/react/column-resizing/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/react/column-resizing/vite.config.js b/examples/react/column-resizing/vite.config.js new file mode 100644 index 0000000000..1755f6bea8 --- /dev/null +++ b/examples/react/column-resizing/vite.config.js @@ -0,0 +1,23 @@ +import { defineConfig } from 'vite' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), + ], +}) diff --git a/examples/react/column-sizing/index.html b/examples/react/column-sizing/index.html index 3fc40c9367..1551290f72 100644 --- a/examples/react/column-sizing/index.html +++ b/examples/react/column-sizing/index.html @@ -4,7 +4,7 @@ Vite App - +
diff --git a/examples/react/column-sizing/package.json b/examples/react/column-sizing/package.json index 17faad0ac6..00e58ae990 100644 --- a/examples/react/column-sizing/package.json +++ b/examples/react/column-sizing/package.json @@ -1,24 +1,28 @@ { - "name": "tanstack-table-example-column-sizing", - "version": "0.0.0", + "name": "tanstack-react-table-example-column-sizing", "private": true, "scripts": { "dev": "vite", "build": "vite build", - "serve": "vite preview --port 3001", - "start": "vite" + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" }, "dependencies": { - "@tanstack/react-table": "^8.20.6", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "@faker-js/faker": "^10.4.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6" }, "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/react/column-sizing/src/index.css b/examples/react/column-sizing/src/index.css index 5a32330d58..d7f648b224 100644 --- a/examples/react/column-sizing/src/index.css +++ b/examples/react/column-sizing/src/index.css @@ -11,6 +11,8 @@ table, .divTable { border: 1px solid lightgray; width: fit-content; + border-collapse: collapse; + border-spacing: 0; } .tr { @@ -78,3 +80,340 @@ td, opacity: 1; } } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/column-sizing/src/main.tsx b/examples/react/column-sizing/src/main.tsx index 086d950775..d41d3ce5f8 100644 --- a/examples/react/column-sizing/src/main.tsx +++ b/examples/react/column-sizing/src/main.tsx @@ -1,457 +1,278 @@ import React from 'react' import ReactDOM from 'react-dom/client' - -import './index.css' - import { - useReactTable, - ColumnResizeMode, - getCoreRowModel, - ColumnDef, - flexRender, - ColumnResizeDirection, + columnSizingFeature, + createColumnHelper, + tableFeatures, + useTable, } from '@tanstack/react-table' +import { makeData } from './makeData' +import type { Person } from './makeData' +import './index.css' -type Person = { - firstName: string - lastName: string - age: number - visits: number - status: string - progress: number -} - -const defaultData: Person[] = [ - { - firstName: 'tanner', - lastName: 'linsley', - age: 24, - visits: 100, - status: 'In Relationship', - progress: 50, - }, - { - firstName: 'tandy', - lastName: 'miller', - age: 40, - visits: 40, - status: 'Single', - progress: 80, - }, - { - firstName: 'joe', - lastName: 'dirte', - age: 45, - visits: 20, - status: 'Complicated', - progress: 10, - }, -] +const _features = tableFeatures({ columnSizingFeature }) -const defaultColumns: ColumnDef[] = [ - { - header: 'Name', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'firstName', - cell: info => info.getValue(), - footer: props => props.column.id, - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - cell: info => info.getValue(), - header: () => Last Name, - footer: props => props.column.id, - }, - ], - }, - { - header: 'Info', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'age', - header: () => 'Age', - footer: props => props.column.id, - }, - { - header: 'More Info', - columns: [ - { - accessorKey: 'visits', - header: () => Visits, - footer: props => props.column.id, - }, - { - accessorKey: 'status', - header: 'Status', - footer: props => props.column.id, - }, - { - accessorKey: 'progress', - header: 'Profile Progress', - footer: props => props.column.id, - }, - ], - }, - ], - }, -] +const columnHelper = createColumnHelper() +// This is not the Column Resizing Example, this is a simplified version that just sets static column sizes function App() { - const [data] = React.useState(() => [...defaultData]) - const [columns] = React.useState(() => [ - ...defaultColumns, - ]) - - const [columnResizeMode, setColumnResizeMode] = - React.useState('onChange') + const [data, setData] = React.useState(() => makeData(20)) + const refreshData = () => setData(makeData(20)) + const stressTest = () => setData(makeData(1_000)) - const [columnResizeDirection, setColumnResizeDirection] = - React.useState('ltr') + const columns = React.useMemo( + () => + columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + size: 120, // initial size + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + size: 120, + }), + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + size: 100, + }), + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + size: 80, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + size: 200, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + size: 200, + }), + ]), + [], + ) const rerender = React.useReducer(() => ({}), {})[1] - const table = useReactTable({ - data, - columns, - columnResizeMode, - columnResizeDirection, - getCoreRowModel: getCoreRowModel(), - debugTable: true, - debugHeaders: true, - debugColumns: true, - }) + const table = useTable( + { + _features, + _rowModels: {}, + columns, + data, + debugTable: true, + debugHeaders: true, + debugColumns: true, + }, + (state) => state, // default selector + ) return ( -
- - -
-
-
{''} -
-
- - {table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(header => ( - - ))} - - ))} - - - {table.getRowModel().rows.map(row => ( - - {row.getVisibleCells().map(cell => ( - - ))} - - ))} - -
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} -
header.column.resetSize(), - onMouseDown: header.getResizeHandler(), - onTouchStart: header.getResizeHandler(), - className: `resizer ${ - table.options.columnResizeDirection - } ${ - header.column.getIsResizing() ? 'isResizing' : '' - }`, - style: { - transform: - columnResizeMode === 'onEnd' && - header.column.getIsResizing() - ? `translateX(${ - (table.options.columnResizeDirection === - 'rtl' - ? -1 - : 1) * - (table.getState().columnSizingInfo - .deltaOffset ?? 0) - }px)` - : '', - }, - }} - /> -
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} -
-
-
-
{'
(relative)'}
-
-
-
- {table.getHeaderGroups().map(headerGroup => ( -
- {headerGroup.headers.map(header => ( -
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} -
header.column.resetSize(), - onMouseDown: header.getResizeHandler(), - onTouchStart: header.getResizeHandler(), - className: `resizer ${ - table.options.columnResizeDirection - } ${ - header.column.getIsResizing() ? 'isResizing' : '' - }`, - style: { - transform: - columnResizeMode === 'onEnd' && - header.column.getIsResizing() - ? `translateX(${ - (table.options.columnResizeDirection === - 'rtl' - ? -1 - : 1) * - (table.getState().columnSizingInfo - .deltaOffset ?? 0) - }px)` - : '', - }, - }} - /> -
- ))} -
- ))} -
-
- {table.getRowModel().rows.map(row => ( -
- {row.getVisibleCells().map(cell => ( -
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} -
- ))} -
- ))} -
+
+
+ + +
+
+
+
{'Initial Column Sizes'}
+
+ {table.getAllColumns().map((column) => ( +
+ +
+ ))} +
+
+
+
{''} +
+
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getAllCells().map((cell) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( + + )} +
+
+ +
+
+
+
{'
(relative)'}
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => ( +
+ {headerGroup.headers.map((header) => ( +
+ {header.isPlaceholder ? null : ( + + )} +
+
+ ))} +
+ ))} +
+
+ {table.getRowModel().rows.map((row) => ( +
+ {row.getAllCells().map((cell) => ( +
+ +
+ ))} +
+ ))}
-
-
{'
(absolute positioning)'}
-
-
-
- {table.getHeaderGroups().map(headerGroup => ( -
- {headerGroup.headers.map(header => ( -
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} -
header.column.resetSize(), - onMouseDown: header.getResizeHandler(), - onTouchStart: header.getResizeHandler(), - className: `resizer ${ - table.options.columnResizeDirection - } ${ - header.column.getIsResizing() ? 'isResizing' : '' - }`, - style: { - transform: - columnResizeMode === 'onEnd' && - header.column.getIsResizing() - ? `translateX(${ - (table.options.columnResizeDirection === - 'rtl' - ? -1 - : 1) * - (table.getState().columnSizingInfo - .deltaOffset ?? 0) - }px)` - : '', - }, - }} - /> -
- ))} -
- ))} -
-
- {table.getRowModel().rows.map(row => ( -
- {row.getVisibleCells().map(cell => ( -
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} -
- ))} -
- ))} -
+
+
+
{'
(absolute positioning)'}
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => ( +
+ {headerGroup.headers.map((header) => ( +
+ {header.isPlaceholder ? null : ( + + )} +
+
+ ))} +
+ ))} +
+
+ {table.getRowModel().rows.map((row) => ( +
+ {row.getAllCells().map((cell) => ( +
+ +
+ ))} +
+ ))}
-
- -
-        {JSON.stringify(
-          {
-            columnSizing: table.getState().columnSizing,
-            columnSizingInfo: table.getState().columnSizingInfo,
-          },
-          null,
-          2
-        )}
-      
+
{JSON.stringify(table.state, null, 2)}
) } @@ -462,5 +283,5 @@ if (!rootElement) throw new Error('Failed to find the root element') ReactDOM.createRoot(rootElement).render( - + , ) diff --git a/examples/react/column-sizing/src/makeData.ts b/examples/react/column-sizing/src/makeData.ts new file mode 100644 index 0000000000..fc070cd5d2 --- /dev/null +++ b/examples/react/column-sizing/src/makeData.ts @@ -0,0 +1,52 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string | undefined + age: number + visits: number | undefined + progress: number + status: 'relationship' | 'complicated' | 'single' + rank: number + createdAt: Date + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: Math.random() < 0.1 ? undefined : faker.person.lastName(), + age: faker.number.int(40), + visits: Math.random() < 0.1 ? undefined : faker.number.int(1000), + progress: faker.number.int(100), + createdAt: faker.date.anytime(), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + rank: faker.number.int(100), + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/react/column-sizing/src/vite-env.d.ts b/examples/react/column-sizing/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/column-sizing/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/column-sizing/tsconfig.json b/examples/react/column-sizing/tsconfig.json index 6d545f543f..33a3e872be 100644 --- a/examples/react/column-sizing/tsconfig.json +++ b/examples/react/column-sizing/tsconfig.json @@ -5,20 +5,17 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, - "include": ["src"] + "include": ["src", "vite.config.js", "vite.config.ts"] } diff --git a/examples/react/column-sizing/vite.config.js b/examples/react/column-sizing/vite.config.js index 2e1361723a..1755f6bea8 100644 --- a/examples/react/column-sizing/vite.config.js +++ b/examples/react/column-sizing/vite.config.js @@ -1,5 +1,6 @@ import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' import rollupReplace from '@rollup/plugin-replace' // https://vitejs.dev/config/ @@ -13,5 +14,10 @@ export default defineConfig({ }, }), react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), ], }) diff --git a/examples/react/column-visibility/index.html b/examples/react/column-visibility/index.html index 3fc40c9367..1551290f72 100644 --- a/examples/react/column-visibility/index.html +++ b/examples/react/column-visibility/index.html @@ -4,7 +4,7 @@ Vite App - +
diff --git a/examples/react/column-visibility/package.json b/examples/react/column-visibility/package.json index d5b9f58ef7..094c17cc2a 100644 --- a/examples/react/column-visibility/package.json +++ b/examples/react/column-visibility/package.json @@ -1,24 +1,28 @@ { - "name": "tanstack-table-example-column-visibility", - "version": "0.0.0", + "name": "tanstack-react-table-example-column-visibility", "private": true, "scripts": { "dev": "vite", "build": "vite build", "serve": "vite preview", - "start": "vite" + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" }, "dependencies": { - "@tanstack/react-table": "^8.20.6", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "@faker-js/faker": "^10.4.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6" }, "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/react/column-visibility/src/index.css b/examples/react/column-visibility/src/index.css index 43c09e0f6b..b9af6ded32 100644 --- a/examples/react/column-visibility/src/index.css +++ b/examples/react/column-visibility/src/index.css @@ -5,6 +5,8 @@ html { table { border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; } tbody { @@ -24,3 +26,340 @@ tfoot { tfoot th { font-weight: normal; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/column-visibility/src/main.tsx b/examples/react/column-visibility/src/main.tsx index cf35b07f05..fee9f4733a 100644 --- a/examples/react/column-visibility/src/main.tsx +++ b/examples/react/column-visibility/src/main.tsx @@ -1,150 +1,114 @@ import React from 'react' import ReactDOM from 'react-dom/client' - -import './index.css' - import { - ColumnDef, - flexRender, - getCoreRowModel, - useReactTable, + columnVisibilityFeature, + createColumnHelper, + tableFeatures, + useTable, } from '@tanstack/react-table' +import { makeData } from './makeData' +import type { Person } from './makeData' +import './index.css' -type Person = { - firstName: string - lastName: string - age: number - visits: number - status: string - progress: number -} +const _features = tableFeatures({ columnVisibilityFeature }) -const defaultData: Person[] = [ - { - firstName: 'tanner', - lastName: 'linsley', - age: 24, - visits: 100, - status: 'In Relationship', - progress: 50, - }, - { - firstName: 'tandy', - lastName: 'miller', - age: 40, - visits: 40, - status: 'Single', - progress: 80, - }, - { - firstName: 'joe', - lastName: 'dirte', - age: 45, - visits: 20, - status: 'Complicated', - progress: 10, - }, -] +const columnHelper = createColumnHelper() -const defaultColumns: ColumnDef[] = [ - { +const columns = columnHelper.columns([ + columnHelper.group({ header: 'Name', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'firstName', - cell: info => info.getValue(), - footer: props => props.column.id, - }, - { - accessorFn: row => row.lastName, + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { id: 'lastName', - cell: info => info.getValue(), + cell: (info) => info.getValue(), header: () => Last Name, - footer: props => props.column.id, - }, - ], - }, - { + footer: (props) => props.column.id, + }), + ]), + }), + columnHelper.group({ header: 'Info', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'age', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('age', { header: () => 'Age', - footer: props => props.column.id, - }, - { + footer: (props) => props.column.id, + }), + columnHelper.group({ header: 'More Info', - columns: [ - { - accessorKey: 'visits', + columns: columnHelper.columns([ + columnHelper.accessor('visits', { header: () => Visits, - footer: props => props.column.id, - }, - { - accessorKey: 'status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { header: 'Status', - footer: props => props.column.id, - }, - { - accessorKey: 'progress', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { header: 'Profile Progress', - footer: props => props.column.id, - }, - ], - }, - ], - }, -] + footer: (props) => props.column.id, + }), + ]), + }), + ]), + }), +]) function App() { - const [data, setData] = React.useState(() => [...defaultData]) - const [columns] = React.useState(() => [ - ...defaultColumns, - ]) - const [columnVisibility, setColumnVisibility] = React.useState({}) - + const [data, setData] = React.useState(() => makeData(20)) + const refreshData = () => setData(makeData(20)) + const stressTest = () => setData(makeData(1_000)) const rerender = React.useReducer(() => ({}), {})[1] - const table = useReactTable({ - data, - columns, - state: { - columnVisibility, + const table = useTable( + { + _features, + _rowModels: {}, + columns, + data, + debugTable: true, + debugHeaders: true, + debugColumns: true, }, - onColumnVisibilityChange: setColumnVisibility, - getCoreRowModel: getCoreRowModel(), - debugTable: true, - debugHeaders: true, - debugColumns: true, - }) + (state) => state, // default selector + ) return ( -
-
-
+
+
+ + +
+
+
+
- {table.getAllLeafColumns().map(column => { + {table.getAllLeafColumns().map((column) => { return ( -
+
@@ -152,58 +116,52 @@ function App() { ) })}
-
+
- {table.getHeaderGroups().map(headerGroup => ( + {table.getHeaderGroups().map((headerGroup) => ( - {headerGroup.headers.map(header => ( + {headerGroup.headers.map((header) => ( ))} ))} - {table.getRowModel().rows.map(row => ( + {table.getRowModel().rows.map((row) => ( - {row.getVisibleCells().map(cell => ( + {row.getVisibleCells().map((cell) => ( ))} ))} - {table.getFooterGroups().map(footerGroup => ( + {table.getFooterGroups().map((footerGroup) => ( - {footerGroup.headers.map(header => ( + {footerGroup.headers.map((header) => ( ))} ))}
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} + {header.isPlaceholder ? null : ( + + )}
- {flexRender(cell.column.columnDef.cell, cell.getContext())} +
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.footer, - header.getContext() - )} + {header.isPlaceholder ? null : ( + + )}
-
- -
-
{JSON.stringify(table.getState().columnVisibility, null, 2)}
+
+
{JSON.stringify(table.state, null, 2)}
) } @@ -214,5 +172,5 @@ if (!rootElement) throw new Error('Failed to find the root element') ReactDOM.createRoot(rootElement).render( - + , ) diff --git a/examples/react/column-visibility/src/makeData.ts b/examples/react/column-visibility/src/makeData.ts new file mode 100644 index 0000000000..fc070cd5d2 --- /dev/null +++ b/examples/react/column-visibility/src/makeData.ts @@ -0,0 +1,52 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string | undefined + age: number + visits: number | undefined + progress: number + status: 'relationship' | 'complicated' | 'single' + rank: number + createdAt: Date + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: Math.random() < 0.1 ? undefined : faker.person.lastName(), + age: faker.number.int(40), + visits: Math.random() < 0.1 ? undefined : faker.number.int(1000), + progress: faker.number.int(100), + createdAt: faker.date.anytime(), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + rank: faker.number.int(100), + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/react/column-visibility/src/vite-env.d.ts b/examples/react/column-visibility/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/column-visibility/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/column-visibility/tsconfig.json b/examples/react/column-visibility/tsconfig.json index 6d545f543f..33a3e872be 100644 --- a/examples/react/column-visibility/tsconfig.json +++ b/examples/react/column-visibility/tsconfig.json @@ -5,20 +5,17 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, - "include": ["src"] + "include": ["src", "vite.config.js", "vite.config.ts"] } diff --git a/examples/react/column-visibility/vite.config.js b/examples/react/column-visibility/vite.config.js index 2e1361723a..1755f6bea8 100644 --- a/examples/react/column-visibility/vite.config.js +++ b/examples/react/column-visibility/vite.config.js @@ -1,5 +1,6 @@ import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' import rollupReplace from '@rollup/plugin-replace' // https://vitejs.dev/config/ @@ -13,5 +14,10 @@ export default defineConfig({ }, }), react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), ], }) diff --git a/examples/react/composable-tables/README.md b/examples/react/composable-tables/README.md new file mode 100644 index 0000000000..a90c07aa5f --- /dev/null +++ b/examples/react/composable-tables/README.md @@ -0,0 +1,135 @@ +# Large Table Example + +This example demonstrates the `createTableHook` composition pattern for TanStack Table v9, similar to TanStack Form's `createFormHook`. + +## What This Demonstrates + +### Single Source of Truth (`createTableHook`) + +The `hooks/table.ts` file sets up everything in one place: + +- **Features** - `_features` defines which table features are enabled +- **Row Models** - Pre-configured sorted, filtered, and paginated row models +- **Default Options** - Any table options can be set as defaults (except columns/data/store/state/initialState) +- **Contexts** - Created internally, with `TFeatures` already baked in +- **Context Hooks** - `useTableContext`, `useCellContext`, `useHeaderContext` - all typed! +- **Pre-bound Components** - Table, cell, and header components +- **Column Helper** - `createAppColumnHelper` pre-bound to your features + +### No Generics Needed in Components + +Because `TFeatures` is baked into the context hooks at creation time, your custom components don't need type annotations: + +```tsx +// components/table-components.tsx +function PaginationControls() { + const table = useTableContext() // TFeatures already known! + return s.pagination}>... +} +``` + +### Simplified Column Helper + +Since `TFeatures` is configured once in `createTableHook`, the `createAppColumnHelper` only needs `TData`: + +```tsx +// TFeatures already bound - only need TData! +const columnHelper = createAppColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { header: 'First Name' }), + columnHelper.accessor('age', { header: 'Age' }), +]) +``` + +### Simplified useAppTable + +Since `_features`, `_rowModels`, and default options are configured once in `createTableHook`, `useAppTable` only needs `columns` and `data`: + +```tsx +const table = useAppTable({ + columns, + data, // TData inferred from this +}) +``` + +### Pre-bound Components + +**Table Components** (accessible via `table.ComponentName`): + +- `PaginationControls` - Full pagination UI +- `RowCount` - Row count display +- `TableToolbar` - Header with title and actions + +**Cell Components** (accessible via `cell.ComponentName` in `AppCell`): + +- `TextCell` - Generic text renderer +- `NumberCell` - Formatted number renderer +- `StatusCell` - Status badge +- `ProgressCell` - Progress bar +- `RowActionsCell` - Row action buttons (view, edit, delete) + +**Header Components** (accessible via `header.ComponentName` in `AppHeader`): + +- `SortIndicator` - Sort direction icon +- `ColumnFilter` - Filter input + +**Footer Components** (accessible via `footer.ComponentName` in `AppFooter`): + +- `FooterColumnId` - Display the column ID +- `FooterSum` - Sum aggregation for numeric columns + +### App Wrapper Components + +The `useAppTable` hook returns these wrapper components: + +- `AppTable` - Root wrapper providing table context +- `AppCell` - Cell wrapper with pre-bound cellComponents +- `AppHeader` - Header wrapper with pre-bound headerComponents +- `AppFooter` - Footer wrapper with pre-bound headerComponents + +### Subscribe Integration + +All App wrapper components support an optional `selector` prop for optimized re-renders: + +```tsx +// Without selector - children is a function receiving the entity + + {(cell) => } + + +// With selector - children receives both entity and selected state + state.columnFilters}> + {(cell, filters) => {filters.length} filters} + + +// AppTable with selector + state.pagination}> + {(pagination) =>
Page {pagination.pageIndex + 1}
} +
+``` + +This wraps the children in a `Subscribe` component for fine-grained reactivity. + +## File Structure + +``` +src/ +├── hooks/ +│ └── table.ts # createTableHook setup with features, row models, and components +├── components/ +│ ├── cell-components.tsx # TextCell, NumberCell, StatusCell, ProgressCell, RowActionsCell +│ ├── header-components.tsx # SortIndicator, ColumnFilter +│ └── table-components.tsx # PaginationControls, RowCount, TableToolbar +├── main.tsx # App entry point with table component +├── makeData.ts # Mock data generator +└── index.css # Styles +``` + +## Running the Example + +```bash +cd examples/react/large-table +pnpm install +pnpm dev +``` diff --git a/examples/react/composable-tables/index.html b/examples/react/composable-tables/index.html new file mode 100644 index 0000000000..a537c64626 --- /dev/null +++ b/examples/react/composable-tables/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Table - Large Table Example with createTableHook + + +
+ + + diff --git a/examples/react/composable-tables/package.json b/examples/react/composable-tables/package.json new file mode 100644 index 0000000000..523e86b154 --- /dev/null +++ b/examples/react/composable-tables/package.json @@ -0,0 +1,29 @@ +{ + "name": "tanstack-react-table-example-composable-tables", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/react-store": "^0.11.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6" + }, + "devDependencies": { + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/react/composable-tables/src/components/cell-components.tsx b/examples/react/composable-tables/src/components/cell-components.tsx new file mode 100644 index 0000000000..29c0c22ee9 --- /dev/null +++ b/examples/react/composable-tables/src/components/cell-components.tsx @@ -0,0 +1,107 @@ +/** + * Cell-level components that use useCellContext + * + * These components can be used via the pre-bound cellComponents + * in AppCell children, e.g., + */ +import { useCellContext } from '../hooks/table' + +/** + * Generic text cell renderer + */ +export function TextCell() { + const cell = useCellContext() + return {cell.getValue()} +} + +/** + * Number cell with locale formatting + */ +export function NumberCell() { + const cell = useCellContext() + return {cell.getValue().toLocaleString()} +} + +/** + * Status badge cell for status column + */ +export function StatusCell() { + const cell = useCellContext<'relationship' | 'complicated' | 'single'>() + const status = cell.getValue() + return {status} +} + +/** + * Progress bar cell + */ +export function ProgressCell() { + const cell = useCellContext() + const progress = cell.getValue() + return ( +
+
+
+ ) +} + +/** + * Row actions cell - actions for the current row + */ +export function RowActionsCell() { + const cell = useCellContext() + const row = cell.row + + return ( +
+ + + +
+ ) +} + +/** + * Price cell with currency formatting + */ +export function PriceCell() { + const cell = useCellContext() + return ( + + $ + {cell.getValue().toLocaleString(undefined, { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })} + + ) +} + +/** + * Category badge cell + */ +export function CategoryCell() { + const cell = useCellContext<'electronics' | 'clothing' | 'food' | 'books'>() + const category = cell.getValue() + return {category} +} diff --git a/examples/react/composable-tables/src/components/header-components.tsx b/examples/react/composable-tables/src/components/header-components.tsx new file mode 100644 index 0000000000..ab5d69754c --- /dev/null +++ b/examples/react/composable-tables/src/components/header-components.tsx @@ -0,0 +1,83 @@ +/** + * Header-level components that use useHeaderContext + * + * These components can be used via the pre-bound headerComponents + * in AppHeader children, e.g., + */ +import { shallow, useSelector } from '@tanstack/react-store' +import { useHeaderContext, useTableContext } from '../hooks/table' + +/** + * Sort indicator showing current sort direction + */ +export function SortIndicator() { + const header = useHeaderContext() + const sorted = header.column.getIsSorted() + + if (!sorted) return null + + return ( + {sorted === 'asc' ? '🔼' : '🔽'} + ) +} + +/** + * Column filter input + * + * Subscribes to `table.store` for this column’s filter value so the controlled input + * stays in sync under the React compiler (a stable `header` from context alone is not enough). + */ +export function ColumnFilter() { + const header = useHeaderContext() + const table = useTableContext() + const columnId = header.column.id + + const columnFilterValue = useSelector( + table.store, + (state) => + state.columnFilters.find((f) => f.id === columnId)?.value as + | string + | undefined, + { compare: shallow }, + ) + + if (!header.column.getCanFilter()) return null + + return ( +
e.stopPropagation()}> + header.column.setFilterValue(e.target.value)} + placeholder={`Filter ${header.column.id}...`} + /> +
+ ) +} + +/** + * Footer showing the column ID + */ +export function FooterColumnId() { + const header = useHeaderContext() + return {header.column.id} +} + +/** + * Footer showing a summary/aggregation for numeric columns + */ +export function FooterSum() { + const header = useHeaderContext() + const table = header.getContext().table + const rows = table.getFilteredRowModel().rows + + // Calculate sum for numeric columns + const sum = rows.reduce((acc, row) => { + const value = row.getValue(header.column.id) + return acc + (typeof value === 'number' ? value : 0) + }, 0) + + return ( + {sum > 0 ? sum.toLocaleString() : '—'} + ) +} diff --git a/examples/react/composable-tables/src/components/table-components.tsx b/examples/react/composable-tables/src/components/table-components.tsx new file mode 100644 index 0000000000..43164c38ef --- /dev/null +++ b/examples/react/composable-tables/src/components/table-components.tsx @@ -0,0 +1,120 @@ +/** + * Table-level components that use useTableContext + * + * These components can be used via the pre-bound tableComponents + * directly on the table object, e.g., + */ +import { useTableContext } from '../hooks/table' + +/** + * Pagination controls for the table + */ +export function PaginationControls() { + const table = useTableContext() + + return ( +
+ + + + + + Page{' '} + + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + + + + | Go to page: + { + const page = e.target.value ? Number(e.target.value) - 1 : 0 + table.setPageIndex(page) + }} + /> + + +
+ ) +} + +/** + * Row count display + */ +export function RowCount() { + const table = useTableContext() + + return ( +
+ Showing {table.getRowModel().rows.length.toLocaleString()} of{' '} + {table.getRowCount().toLocaleString()} rows +
+ ) +} + +/** + * Table toolbar with title and actions + */ +export function TableToolbar({ + title, + onRefresh, + onStressTest, +}: { + title: string + onRefresh?: () => void + onStressTest?: () => void +}) { + const table = useTableContext() + + return ( +
+

{title}

+
+ {onRefresh && } + {onStressTest && ( + + )} + + +
+
+ ) +} diff --git a/examples/react/composable-tables/src/hooks/table.ts b/examples/react/composable-tables/src/hooks/table.ts new file mode 100644 index 0000000000..f40774f901 --- /dev/null +++ b/examples/react/composable-tables/src/hooks/table.ts @@ -0,0 +1,105 @@ +/** + * Custom table hook setup using createTableHook + * + * This file creates a custom useAppTable hook with pre-bound components. + * Features, row models, and default options are defined once here and shared across all tables. + * Context hooks and a pre-bound createAppColumnHelper are also exported. + */ +import { + columnFilteringFeature, + createFilteredRowModel, + createPaginatedRowModel, + createSortedRowModel, + createTableHook, + filterFns, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/react-table' + +// Import table-level components +import { + PaginationControls, + RowCount, + TableToolbar, +} from '../components/table-components' + +// Import cell-level components +import { + CategoryCell, + NumberCell, + PriceCell, + ProgressCell, + RowActionsCell, + StatusCell, + TextCell, +} from '../components/cell-components' + +// Import header/footer-level components (both use useHeaderContext) +import { + ColumnFilter, + FooterColumnId, + FooterSum, + SortIndicator, +} from '../components/header-components' + +/** + * Create the custom table hook with all pre-bound components. + * This exports: + * - createAppColumnHelper: Create column definitions with TFeatures already bound + * - useAppTable: Hook for creating tables with TFeatures baked in + * - useTableContext: Access table instance in tableComponents + * - useCellContext: Access cell instance in cellComponents + * - useHeaderContext: Access header instance in headerComponents + */ +export const { + createAppColumnHelper, + useAppTable, + useTableContext, + useCellContext, + useHeaderContext, +} = createTableHook({ + // Features are set once here and shared across all tables + _features: tableFeatures({ + columnFilteringFeature, + rowPaginationFeature, + rowSortingFeature, + }), + + // Row models are set once here + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + }, + + // set any default table options here too + getRowId: (row) => row.id, + + // Register table-level components (accessible via table.ComponentName) + tableComponents: { + PaginationControls, + RowCount, + TableToolbar, + }, + + // Register cell-level components (accessible via cell.ComponentName in AppCell) + cellComponents: { + TextCell, + NumberCell, + StatusCell, + ProgressCell, + RowActionsCell, + PriceCell, + CategoryCell, + }, + + // Register header/footer-level components (accessible via header.ComponentName in AppHeader/AppFooter) + headerComponents: { + SortIndicator, + ColumnFilter, + FooterColumnId, + FooterSum, + }, +}) diff --git a/examples/react/composable-tables/src/index.css b/examples/react/composable-tables/src/index.css new file mode 100644 index 0000000000..4d8fef0142 --- /dev/null +++ b/examples/react/composable-tables/src/index.css @@ -0,0 +1,605 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + width: 100%; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th, +td { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 8px 12px; + text-align: left; +} + +th { + background-color: #f5f5f5; + font-weight: 600; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +.table-container { + padding: 16px; + max-width: 1200px; + margin: 0 auto; + border-collapse: collapse; + border-spacing: 0; +} + +.pagination { + display: flex; + align-items: center; + gap: 8px; + margin-top: 16px; + flex-wrap: wrap; +} + +.pagination button { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px 8px; + cursor: pointer; + background: white; +} + +.pagination button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.pagination input { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px; + width: 64px; +} + +.pagination select { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px; +} + +.sort-indicator { + margin-left: 4px; +} + +.column-filter { + margin-top: 4px; +} + +.column-filter input { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px; + width: 100%; + font-size: 12px; +} + +.sortable-header { + cursor: pointer; + user-select: none; +} + +.sortable-header:hover { + background-color: #e8e8e8; +} + +.row-actions { + display: flex; + gap: 4px; +} + +.row-actions button { + border: 1px solid #ccc; + border-radius: 4px; + padding: 2px 8px; + cursor: pointer; + background: white; + font-size: 12px; +} + +.row-actions button:hover { + background-color: #f0f0f0; +} + +.status-badge { + display: inline-block; + padding: 2px 8px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; +} + +.status-badge.relationship { + background-color: #d4edda; + color: #155724; +} + +.status-badge.complicated { + background-color: #fff3cd; + color: #856404; +} + +.status-badge.single { + background-color: #cce5ff; + color: #004085; +} + +.progress-bar { + width: 100%; + height: 8px; + background-color: #e9ecef; + border-radius: 4px; + overflow: hidden; +} + +.progress-bar-fill { + height: 100%; + background-color: #007bff; + transition: width 0.2s; +} + +.table-toolbar { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + flex-wrap: wrap; + gap: 8px; + border-collapse: collapse; + border-spacing: 0; +} + +.table-toolbar h2 { + margin: 0; + border-collapse: collapse; + border-spacing: 0; +} + +.table-toolbar-actions { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.table-toolbar button { + border: 1px solid #ccc; + border-radius: 4px; + padding: 8px 16px; + cursor: pointer; + background: white; + border-collapse: collapse; + border-spacing: 0; +} + +.table-toolbar button:hover { + background-color: #f0f0f0; + border-collapse: collapse; + border-spacing: 0; +} + +.row-count { + color: #666; + font-size: 14px; + margin-top: 8px; +} + +.app { + padding: 16px; +} + +.app h1 { + text-align: center; + margin-bottom: 8px; +} + +.description { + text-align: center; + color: #666; + margin-bottom: 32px; +} + +.description code { + background-color: #f5f5f5; + padding: 2px 6px; + border-radius: 4px; + font-size: 13px; +} + +.table-divider { + height: 48px; + border-bottom: 1px solid #e0e0e0; + margin: 32px auto; + max-width: 1200px; + border-collapse: collapse; + border-spacing: 0; +} + +.category-badge { + display: inline-block; + padding: 2px 8px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; + text-transform: capitalize; +} + +.category-badge.electronics { + background-color: #e3f2fd; + color: #1565c0; +} + +.category-badge.clothing { + background-color: #fce4ec; + color: #c2185b; +} + +.category-badge.food { + background-color: #e8f5e9; + color: #2e7d32; +} + +.category-badge.books { + background-color: #fff8e1; + color: #f57c00; +} + +.price { + font-weight: 600; + color: #2e7d32; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/composable-tables/src/main.tsx b/examples/react/composable-tables/src/main.tsx new file mode 100644 index 0000000000..63239f86f5 --- /dev/null +++ b/examples/react/composable-tables/src/main.tsx @@ -0,0 +1,440 @@ +import * as React from 'react' +import { useCallback, useMemo, useState } from 'react' +import ReactDOM from 'react-dom/client' +import { createAppColumnHelper, useAppTable } from './hooks/table' +import { makeData, makeProductData } from './makeData' +import type { Person, Product } from './makeData' +import './index.css' +// Import cell components directly - they use useCellContext internally + +// Create column helpers with TFeatures already bound - only need TData! +const personColumnHelper = createAppColumnHelper() +const productColumnHelper = createAppColumnHelper() + +// Users Table Component - Original implementation +function UsersTable() { + // Data state + const [data, setData] = useState(() => makeData(1000)) + + // Refresh data callback + const refreshData = useCallback(() => { + setData(makeData(1000)) + }, []) + + const stressTest = useCallback(() => { + setData(makeData(200_000)) + }, []) + + // Define columns using the column helper + const columns = useMemo( + () => + // NOTE: You must use `createAppColumnHelper` instead of `createColumnHelper` when using pre-bound components like + personColumnHelper.columns([ + personColumnHelper.accessor('firstName', { + header: 'First Name', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + personColumnHelper.accessor('lastName', { + header: 'Last Name', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + personColumnHelper.accessor('age', { + header: 'Age', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + personColumnHelper.accessor('visits', { + header: 'Visits', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + personColumnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + personColumnHelper.accessor('progress', { + header: 'Progress', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + personColumnHelper.display({ + id: 'actions', + header: 'Actions', + cell: ({ cell }) => , + }), + ]), + [], + ) + + // Create the table - _features and _rowModels are already configured! + const table = useAppTable( + { + columns, + data, + debugTable: true, + // more table options + }, + (state) => state, // default selector + ) + + return ( + // Main selector on AppTable - selects all needed state in one place + ({ + // subscribe to specific states for re-rendering if you are optimizing for maximum performance + pagination: state.pagination, + sorting: state.sorting, + columnFilters: state.columnFilters, + })} + > + {({ sorting, columnFilters }) => ( +
+ {/* Table toolbar using pre-bound component */} + + + {/* Table element */} + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((h) => ( + + {(header) => ( + + )} + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getAllCells().map((c) => ( + + {(cell) => ( + + )} + + ))} + + ))} + + + {table.getFooterGroups().map((footerGroup) => ( + + {footerGroup.headers.map((f) => ( + + {(footer) => { + const columnId = footer.column.id + const hasFilter = columnFilters.some( + (cf) => cf.id === columnId, + ) + + return ( + + ) + }} + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( + <> + + + + {/* Show sort order number when multiple columns sorted */} + {sorting.length > 1 && + sorting.findIndex( + (s) => s.id === header.column.id, + ) > -1 && ( + + {sorting.findIndex( + (s) => s.id === header.column.id, + ) + 1} + + )} + + )} +
+ {/* Cell components are pre-bound via AppCell */} + +
+ {footer.isPlaceholder ? null : ( + <> + {/* Use FooterSum for numeric columns, FooterColumnId for others */} + {columnId === 'age' || + columnId === 'visits' || + columnId === 'progress' ? ( + <> + + {hasFilter && ( + + {' '} + (filtered) + + )} + + ) : columnId === 'actions' ? null : ( + <> + + {hasFilter && ( + + {' '} + ✓ + + )} + + )} + + )} +
+ + {/* Pagination using pre-bound component */} + + + {/* Row count using pre-bound component */} + +
+ )} +
+ ) +} + +// Products Table Component - New implementation using same hook and components +function ProductsTable() { + // Data state + const [data, setData] = useState(() => makeProductData(500)) + + // Refresh data callback + const refreshData = useCallback(() => { + setData(makeProductData(500)) + }, []) + + const stressTest = useCallback(() => { + setData(makeProductData(200_000)) + }, []) + + // Define columns using the column helper - different structure than Users table + const columns = useMemo( + () => + productColumnHelper.columns([ + productColumnHelper.accessor('name', { + header: 'Product Name', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + productColumnHelper.accessor('category', { + header: 'Category', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + productColumnHelper.accessor('price', { + header: 'Price', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + productColumnHelper.accessor('stock', { + header: 'In Stock', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + productColumnHelper.accessor('rating', { + header: 'Rating', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + ]), + [], + ) + + // Create the table using the same useAppTable hook + const table = useAppTable( + { + debugTable: true, + columns, + data, + getRowId: (row) => row.id, + }, + (state) => state, // default selector + ) + + return ( + ({ + pagination: state.pagination, + sorting: state.sorting, + columnFilters: state.columnFilters, + })} + > + {({ sorting, columnFilters }) => ( +
+ {/* Table toolbar using the same pre-bound component */} + + + {/* Table element */} + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((h) => ( + + {(header) => ( + + )} + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getAllCells().map((c) => ( + + {(cell) => ( + + )} + + ))} + + ))} + + + {table.getFooterGroups().map((footerGroup) => ( + + {footerGroup.headers.map((f) => ( + + {(footer) => { + const columnId = footer.column.id + const hasFilter = columnFilters.some( + (cf) => cf.id === columnId, + ) + + return ( + + ) + }} + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( + <> + + + + {sorting.length > 1 && + sorting.findIndex( + (s) => s.id === header.column.id, + ) > -1 && ( + + {sorting.findIndex( + (s) => s.id === header.column.id, + ) + 1} + + )} + + )} +
+ {/* Cell components are pre-bound via AppCell */} + +
+ {footer.isPlaceholder ? null : ( + <> + {/* Use FooterSum for numeric columns, FooterColumnId for others */} + {columnId === 'price' || + columnId === 'stock' || + columnId === 'rating' ? ( + <> + + {hasFilter && ( + + {' '} + (filtered) + + )} + + ) : ( + <> + + {hasFilter && ( + + {' '} + ✓ + + )} + + )} + + )} +
+ + {/* Pagination using the same pre-bound component */} + + + {/* Row count using the same pre-bound component */} + +
+ )} +
+ ) +} + +function App() { + return ( +
+

Composable Tables Example

+

+ Both tables below use the same useAppTable hook and + shareable components, but with different data types and column + configurations. +

+ + {/* Original Users Table */} + + +
+ + {/* New Products Table */} + +
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +ReactDOM.createRoot(rootElement).render( + + + , +) diff --git a/examples/react/composable-tables/src/makeData.ts b/examples/react/composable-tables/src/makeData.ts new file mode 100644 index 0000000000..17dec1c6de --- /dev/null +++ b/examples/react/composable-tables/src/makeData.ts @@ -0,0 +1,77 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +export type Product = { + id: string + name: string + category: 'electronics' | 'clothing' | 'food' | 'books' + price: number + stock: number + rating: number +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +const newProduct = (): Product => { + return { + id: faker.string.uuid(), + name: faker.commerce.productName(), + category: faker.helpers.shuffle([ + 'electronics', + 'clothing', + 'food', + 'books', + ])[0], + price: parseFloat(faker.commerce.price({ min: 5, max: 500 })), + stock: faker.number.int({ min: 0, max: 200 }), + rating: faker.number.int({ min: 0, max: 100 }), + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} + +export function makeProductData(count: number): Array { + return range(count).map(() => newProduct()) +} diff --git a/examples/react/composable-tables/src/vite-env.d.ts b/examples/react/composable-tables/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/composable-tables/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/composable-tables/tsconfig.json b/examples/react/composable-tables/tsconfig.json new file mode 100644 index 0000000000..33a3e872be --- /dev/null +++ b/examples/react/composable-tables/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/react/composable-tables/vite.config.js b/examples/react/composable-tables/vite.config.js new file mode 100644 index 0000000000..b65b0fda77 --- /dev/null +++ b/examples/react/composable-tables/vite.config.js @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), + ], +}) diff --git a/examples/react/custom-features/index.html b/examples/react/custom-features/index.html deleted file mode 100644 index 3fc40c9367..0000000000 --- a/examples/react/custom-features/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - Vite App - - - -
- - - diff --git a/examples/react/custom-features/package.json b/examples/react/custom-features/package.json deleted file mode 100644 index 9d62a0b25a..0000000000 --- a/examples/react/custom-features/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "tanstack-table-example-custom-features", - "version": "0.0.0", - "private": true, - "scripts": { - "dev": "vite", - "build": "vite build", - "serve": "vite preview", - "start": "vite" - }, - "dependencies": { - "@faker-js/faker": "^8.4.1", - "@tanstack/react-table": "^8.20.6", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "typescript": "5.4.5", - "vite": "^5.3.2" - } -} diff --git a/examples/react/custom-features/src/index.css b/examples/react/custom-features/src/index.css deleted file mode 100644 index 5747ffc905..0000000000 --- a/examples/react/custom-features/src/index.css +++ /dev/null @@ -1,34 +0,0 @@ -html { - font-family: sans-serif; - font-size: 14px; -} - -table { - border: 1px solid lightgray; -} - -tbody { - border-bottom: 1px solid lightgray; -} - -th { - border-bottom: 1px solid lightgray; - border-right: 1px solid lightgray; - padding: 2px 4px; -} - -tfoot { - color: gray; -} - -tfoot th { - font-weight: normal; -} - -tr { - border-bottom: 1px solid lightgray; -} - -button:disabled { - opacity: 0.5; -} diff --git a/examples/react/custom-features/src/main.tsx b/examples/react/custom-features/src/main.tsx deleted file mode 100644 index 5bad334d38..0000000000 --- a/examples/react/custom-features/src/main.tsx +++ /dev/null @@ -1,397 +0,0 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' - -import './index.css' - -import { - useReactTable, - makeStateUpdater, - getSortedRowModel, - getPaginationRowModel, - getFilteredRowModel, - getCoreRowModel, - flexRender, - TableFeature, - Table, - RowData, - OnChangeFn, - ColumnDef, - Column, - Updater, - functionalUpdate, -} from '@tanstack/react-table' - -import { makeData, Person } from './makeData' - -// TypeScript setup for our new feature with all of the same type-safety as stock TanStack Table features - -// define types for our new feature's custom state -export type DensityState = 'sm' | 'md' | 'lg' -export interface DensityTableState { - density: DensityState -} - -// define types for our new feature's table options -export interface DensityOptions { - enableDensity?: boolean - onDensityChange?: OnChangeFn -} - -// Define types for our new feature's table APIs -export interface DensityInstance { - setDensity: (updater: Updater) => 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' { - //merge our new feature's state with the existing table state - interface TableState extends DensityTableState {} - //merge our new feature's options with the existing table options - interface TableOptionsResolved - extends DensityOptions {} - //merge our new feature's instance APIs with the existing table instance APIs - interface Table extends DensityInstance {} - // if you need to add cell instance APIs... - // interface Cell extends DensityCell - // if you need to add row instance APIs... - // interface Row extends DensityRow - // if you need to add column instance APIs... - // interface Column extends DensityColumn - // if you need to add header instance APIs... - // interface Header extends DensityHeader - - // 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` -} - -// end of TS setup! - -// Here is all of the actual javascript code for our new feature -export const DensityFeature: TableFeature = { - // define the new feature's initial state - getInitialState: (state): DensityTableState => { - return { - density: 'md', - ...state, - } - }, - - // define the new feature's default options - getDefaultOptions: ( - table: Table - ): DensityOptions => { - return { - enableDensity: true, - onDensityChange: makeStateUpdater('density', table), - } as DensityOptions - }, - // if you need to add a default column definition... - // getDefaultColumnDef: (): Partial> => { - // 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 - createTable: (table: Table): void => { - table.setDensity = updater => { - const safeUpdater: Updater = old => { - let newState = functionalUpdate(updater, old) - return newState - } - return table.options.onDensityChange?.(safeUpdater) - } - table.toggleDensity = value => { - table.setDensity(old => { - if (value) return value - return old === 'lg' ? 'md' : old === 'md' ? 'sm' : 'lg' //cycle through the 3 options - }) - } - }, - - // if you need to add row instance APIs... - // createRow: (row, table): void => {}, - // if you need to add cell instance APIs... - // createCell: (cell, column, row, table): void => {}, - // if you need to add column instance APIs... - // createColumn: (column, table): void => {}, - // if you need to add header instance APIs... - // createHeader: (header, table): void => {}, -} -//end of custom feature code - -//app code -function App() { - const columns = React.useMemo[]>( - () => [ - { - accessorKey: 'firstName', - cell: info => info.getValue(), - footer: props => props.column.id, - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - cell: info => info.getValue(), - header: () => Last Name, - footer: props => props.column.id, - }, - { - accessorKey: 'age', - header: () => 'Age', - footer: props => props.column.id, - }, - { - accessorKey: 'visits', - header: () => Visits, - footer: props => props.column.id, - }, - { - accessorKey: 'status', - header: 'Status', - footer: props => props.column.id, - }, - { - accessorKey: 'progress', - header: 'Profile Progress', - footer: props => props.column.id, - }, - ], - [] - ) - - const [data, _setData] = React.useState(() => makeData(1000)) - const [density, setDensity] = React.useState('md') - - const table = useReactTable({ - _features: [DensityFeature], //pass our custom feature to the table to be instantiated upon creation - columns, - data, - debugTable: true, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getPaginationRowModel: getPaginationRowModel(), - state: { - density, //passing the density state to the table, TS is still happy :) - }, - onDensityChange: setDensity, //using the new onDensityChange option, TS is still happy :) - }) - - return ( -
-
- - - - {table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(header => { - return ( - - ) - })} - - ))} - - - {table.getRowModel().rows.map(row => { - return ( - - {row.getVisibleCells().map(cell => { - return ( - - ) - })} - - ) - })} - -
-
- {flexRender( - header.column.columnDef.header, - header.getContext() - )} - {{ - asc: ' 🔼', - desc: ' 🔽', - }[header.column.getIsSorted() as string] ?? null} -
- {header.column.getCanFilter() ? ( -
- -
- ) : null} -
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} -
-
-
- - - - - -
Page
- - {table.getState().pagination.pageIndex + 1} of{' '} - {table.getPageCount().toLocaleString()} - -
- - | Go to page: - { - const page = e.target.value ? Number(e.target.value) - 1 : 0 - table.setPageIndex(page) - }} - className="border p-1 rounded w-16" - /> - - -
-
- Showing {table.getRowModel().rows.length.toLocaleString()} of{' '} - {table.getRowCount().toLocaleString()} Rows -
-
{JSON.stringify(table.getState().pagination, null, 2)}
-
- ) -} - -function Filter({ - column, - table, -}: { - column: Column - table: Table -}) { - const firstValue = table - .getPreFilteredRowModel() - .flatRows[0]?.getValue(column.id) - - const columnFilterValue = column.getFilterValue() - - return typeof firstValue === 'number' ? ( -
- - column.setFilterValue((old: [number, number]) => [ - e.target.value, - old?.[1], - ]) - } - placeholder={`Min`} - className="w-24 border shadow rounded" - /> - - column.setFilterValue((old: [number, number]) => [ - old?.[0], - e.target.value, - ]) - } - placeholder={`Max`} - className="w-24 border shadow rounded" - /> -
- ) : ( - column.setFilterValue(e.target.value)} - placeholder={`Search...`} - className="w-36 border shadow rounded" - /> - ) -} - -const rootElement = document.getElementById('root') -if (!rootElement) throw new Error('Failed to find the root element') - -ReactDOM.createRoot(rootElement).render( - - - -) diff --git a/examples/react/custom-features/src/makeData.ts b/examples/react/custom-features/src/makeData.ts deleted file mode 100644 index 331dd1eb19..0000000000 --- a/examples/react/custom-features/src/makeData.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { faker } from '@faker-js/faker' - -export type Person = { - firstName: string - lastName: string - age: number - visits: number - progress: number - status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] -} - -const range = (len: number) => { - const arr: number[] = [] - for (let i = 0; i < len; i++) { - arr.push(i) - } - return arr -} - -const newPerson = (): Person => { - return { - firstName: faker.person.firstName(), - lastName: faker.person.lastName(), - age: faker.number.int(40), - visits: faker.number.int(1000), - progress: faker.number.int(100), - status: faker.helpers.shuffle([ - 'relationship', - 'complicated', - 'single', - ])[0]!, - } -} - -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! - return range(len).map((d): Person => { - return { - ...newPerson(), - subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, - } - }) - } - - return makeDataLevel() -} diff --git a/examples/react/custom-features/tsconfig.json b/examples/react/custom-features/tsconfig.json deleted file mode 100644 index 6d545f543f..0000000000 --- a/examples/react/custom-features/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"] -} diff --git a/examples/react/custom-features/vite.config.js b/examples/react/custom-features/vite.config.js deleted file mode 100644 index 2e1361723a..0000000000 --- a/examples/react/custom-features/vite.config.js +++ /dev/null @@ -1,17 +0,0 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' -import rollupReplace from '@rollup/plugin-replace' - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [ - rollupReplace({ - preventAssignment: true, - values: { - __DEV__: JSON.stringify(true), - 'process.env.NODE_ENV': JSON.stringify('development'), - }, - }), - react(), - ], -}) diff --git a/examples/react/pagination-controlled/.gitignore b/examples/react/custom-plugin/.gitignore similarity index 100% rename from examples/react/pagination-controlled/.gitignore rename to examples/react/custom-plugin/.gitignore diff --git a/examples/react/material-ui-pagination/README.md b/examples/react/custom-plugin/README.md similarity index 100% rename from examples/react/material-ui-pagination/README.md rename to examples/react/custom-plugin/README.md diff --git a/examples/react/custom-plugin/index.html b/examples/react/custom-plugin/index.html new file mode 100644 index 0000000000..1551290f72 --- /dev/null +++ b/examples/react/custom-plugin/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/react/custom-plugin/package.json b/examples/react/custom-plugin/package.json new file mode 100644 index 0000000000..b272b70a94 --- /dev/null +++ b/examples/react/custom-plugin/package.json @@ -0,0 +1,29 @@ +{ + "name": "tanstack-react-table-example-custom-plugin", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/react-pacer": "^0.22.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6" + }, + "devDependencies": { + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/react/custom-plugin/src/index.css b/examples/react/custom-plugin/src/index.css new file mode 100644 index 0000000000..f8ffbb8e90 --- /dev/null +++ b/examples/react/custom-plugin/src/index.css @@ -0,0 +1,373 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +tr { + border-bottom: 1px solid lightgray; +} + +button:disabled { + opacity: 0.5; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/custom-plugin/src/main.tsx b/examples/react/custom-plugin/src/main.tsx new file mode 100644 index 0000000000..cb18a3693f --- /dev/null +++ b/examples/react/custom-plugin/src/main.tsx @@ -0,0 +1,417 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import './index.css' +import { + columnFilteringFeature, + createColumnHelper, + createFilteredRowModel, + createPaginatedRowModel, + createSortedRowModel, + filterFns, + functionalUpdate, + makeStateUpdater, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, + useTable, +} from '@tanstack/react-table' +import { useDebouncedCallback } from '@tanstack/react-pacer/debouncer' +import { makeData } from './makeData' +import type { + Column, + OnChangeFn, + ReactTable, + TableFeature, + Updater, +} from '@tanstack/react-table' +import type { Person } from './makeData' + +// TypeScript setup for our new feature with all of the same type-safety as stock TanStack Table features + +// define types for our new feature's custom state +export type DensityState = 'sm' | 'md' | 'lg' +export interface TableState_Density { + density: DensityState +} + +// define types for our new feature's table options +export interface TableOptions_Density { + enableDensity?: boolean + onDensityChange?: OnChangeFn +} + +// Define types for our new feature's table APIs +export interface Table_Density { + setDensity: (updater: Updater) => void + toggleDensity: (value?: DensityState) => void +} + +interface DensityPluginConstructors { + Table: Table_Density + TableOptions: TableOptions_Density + TableState: TableState_Density +} + +// Here is all of the actual javascript code for our new feature +export const densityPlugin: TableFeature = { + // define the new feature's initial state + getInitialState: (initialState) => { + return { + density: 'md', + ...initialState, // must come last + } + }, + + // define the new feature's default options + getDefaultTableOptions: (table) => { + return { + enableDensity: true, + onDensityChange: makeStateUpdater('density', table), + } + }, + // if you need to add a default column definition... + // getDefaultColumnDef: () => {}, + + // define the new feature's table instance methods + constructTableAPIs: (table) => { + table.setDensity = (updater) => { + const safeUpdater: Updater = (old) => { + const newState = functionalUpdate(updater, old) + return newState + } + return table.options.onDensityChange?.(safeUpdater) + } + table.toggleDensity = (value) => { + table.setDensity?.((old) => { + if (value) return value + return old === 'lg' ? 'md' : old === 'md' ? 'sm' : 'lg' // cycle through the 3 options + }) + } + }, + + // if you need to add row instance APIs... + // constructRowAPIs: (row) => {}, + + // if you need to add cell instance APIs... + // constructCellAPIs: (cell) => {}, + + // if you need to add column instance APIs... + // constructColumnAPIs: (column) => {}, + + // if you need to add header instance APIs... + // constructHeaderAPIs: (header) => {}, +} +// end of custom feature code + +// app code +const _features = tableFeatures({ + columnFilteringFeature, + rowSortingFeature, + rowPaginationFeature, + densityPlugin, // pass in our plugin just like any other stock feature +}) + +const columnHelper = createColumnHelper() + +function App() { + const columns = React.useMemo( + () => + columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }), + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + }), + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), + ]), + [], + ) + + const [data, setData] = React.useState(() => makeData(1_000)) + const refreshData = () => setData(makeData(1_000)) + const stressTest = () => setData(makeData(200_000)) + const [density, setDensity] = React.useState('md') + + const table = useTable( + { + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, + columns, + data, + debugTable: true, + state: { + density, // passing the density state to the table, TS is still happy :) + }, + onDensityChange: setDensity, // using the new onDensityChange option, TS is still happy :) + }, + (state) => state, // default selector + ) + + return ( +
+
+ + +
+
+ + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ) + })} + + ))} + + + {table.getRowModel().rows.map((row) => { + return ( + + {row.getAllCells().map((cell) => { + return ( + + ) + })} + + ) + })} + +
+
+ + {{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null} +
+ {header.column.getCanFilter() ? ( +
+ +
+ ) : null} +
+ +
+
+
+ + + + + +
Page
+ + {(table.store.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = e.target.value ? Number(e.target.value) - 1 : 0 + table.setPageIndex(page) + }} + className="page-size-input" + /> + + +
+
+ Showing {table.getRowModel().rows.length.toLocaleString()} of{' '} + {table.getRowCount().toLocaleString()} Rows +
+
{JSON.stringify(table.state, null, 2)}
+
+ ) +} + +function Filter({ + column, + table, +}: { + column: Column + table: ReactTable +}) { + const firstValue = table + .getPreFilteredRowModel() + .flatRows[0]?.getValue(column.id) + + const columnFilterValue = column.getFilterValue() + + return typeof firstValue === 'number' ? ( +
+ + column.setFilterValue((old: [number, number]) => [value, old?.[1]]) + } + placeholder={`Min`} + className="filter-input" + /> + + column.setFilterValue((old: [number, number]) => [old?.[0], value]) + } + placeholder={`Max`} + className="filter-input" + /> +
+ ) : ( + column.setFilterValue(value)} + placeholder={`Search...`} + className="filter-select" + /> + ) +} + +// A debounced input react component +function DebouncedInput({ + value: initialValue, + onChange, + debounce = 500, + ...props +}: { + value: string | number + onChange: (value: string | number) => void + debounce?: number +} & Omit, 'onChange'>) { + const [value, setValue] = React.useState(initialValue) + + React.useEffect(() => { + setValue(initialValue) + }, [initialValue]) + + const debouncedOnChange = useDebouncedCallback(onChange, { wait: debounce }) + + return ( + { + setValue(e.target.value) + debouncedOnChange(e.target.value) + }} + /> + ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +ReactDOM.createRoot(rootElement).render( + + + , +) diff --git a/examples/react/custom-plugin/src/makeData.ts b/examples/react/custom-plugin/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/react/custom-plugin/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/react/custom-plugin/src/vite-env.d.ts b/examples/react/custom-plugin/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/custom-plugin/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/custom-plugin/tsconfig.json b/examples/react/custom-plugin/tsconfig.json new file mode 100644 index 0000000000..33a3e872be --- /dev/null +++ b/examples/react/custom-plugin/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/react/custom-plugin/vite.config.js b/examples/react/custom-plugin/vite.config.js new file mode 100644 index 0000000000..1755f6bea8 --- /dev/null +++ b/examples/react/custom-plugin/vite.config.js @@ -0,0 +1,23 @@ +import { defineConfig } from 'vite' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), + ], +}) diff --git a/examples/react/editable-data/index.html b/examples/react/editable-data/index.html deleted file mode 100644 index 3fc40c9367..0000000000 --- a/examples/react/editable-data/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - Vite App - - - -
- - - diff --git a/examples/react/editable-data/package.json b/examples/react/editable-data/package.json deleted file mode 100644 index 4aea3a36a5..0000000000 --- a/examples/react/editable-data/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "tanstack-table-example-editable-data", - "version": "0.0.0", - "private": true, - "scripts": { - "dev": "vite", - "build": "vite build", - "serve": "vite preview", - "start": "vite" - }, - "dependencies": { - "@faker-js/faker": "^8.4.1", - "@tanstack/react-table": "^8.20.6", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "typescript": "5.4.5", - "vite": "^5.3.2" - } -} diff --git a/examples/react/editable-data/src/index.css b/examples/react/editable-data/src/index.css deleted file mode 100644 index 43c09e0f6b..0000000000 --- a/examples/react/editable-data/src/index.css +++ /dev/null @@ -1,26 +0,0 @@ -html { - font-family: sans-serif; - font-size: 14px; -} - -table { - border: 1px solid lightgray; -} - -tbody { - border-bottom: 1px solid lightgray; -} - -th { - border-bottom: 1px solid lightgray; - border-right: 1px solid lightgray; - padding: 2px 4px; -} - -tfoot { - color: gray; -} - -tfoot th { - font-weight: normal; -} diff --git a/examples/react/editable-data/src/main.tsx b/examples/react/editable-data/src/main.tsx deleted file mode 100644 index 0b12a9fd8c..0000000000 --- a/examples/react/editable-data/src/main.tsx +++ /dev/null @@ -1,340 +0,0 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' - -// -import './index.css' - -// -import { - Column, - Table, - ColumnDef, - useReactTable, - getCoreRowModel, - getFilteredRowModel, - getPaginationRowModel, - flexRender, - RowData, -} from '@tanstack/react-table' -import { makeData, Person } from './makeData' - -declare module '@tanstack/react-table' { - interface TableMeta { - updateData: (rowIndex: number, columnId: string, value: unknown) => void - } -} - -// Give our default column cell renderer editing superpowers! -const defaultColumn: Partial> = { - cell: ({ getValue, row: { index }, column: { id }, table }) => { - const initialValue = getValue() - // We need to keep and update the state of the cell normally - const [value, setValue] = React.useState(initialValue) - - // When the input is blurred, we'll call our table meta's updateData function - const onBlur = () => { - table.options.meta?.updateData(index, id, value) - } - - // If the initialValue is changed external, sync it up with our state - React.useEffect(() => { - setValue(initialValue) - }, [initialValue]) - - return ( - setValue(e.target.value)} - onBlur={onBlur} - /> - ) - }, -} - -function useSkipper() { - const shouldSkipRef = React.useRef(true) - const shouldSkip = shouldSkipRef.current - - // Wrap a function with this to skip a pagination reset temporarily - const skip = React.useCallback(() => { - shouldSkipRef.current = false - }, []) - - React.useEffect(() => { - shouldSkipRef.current = true - }) - - return [shouldSkip, skip] as const -} - -function App() { - const rerender = React.useReducer(() => ({}), {})[1] - - const columns = React.useMemo[]>( - () => [ - { - header: 'Name', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'firstName', - footer: props => props.column.id, - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - header: () => Last Name, - footer: props => props.column.id, - }, - ], - }, - { - header: 'Info', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'age', - header: () => 'Age', - footer: props => props.column.id, - }, - { - header: 'More Info', - columns: [ - { - accessorKey: 'visits', - header: () => Visits, - footer: props => props.column.id, - }, - { - accessorKey: 'status', - header: 'Status', - footer: props => props.column.id, - }, - { - accessorKey: 'progress', - header: 'Profile Progress', - footer: props => props.column.id, - }, - ], - }, - ], - }, - ], - [] - ) - - const [data, setData] = React.useState(() => makeData(1000)) - const refreshData = () => setData(() => makeData(1000)) - - const [autoResetPageIndex, skipAutoResetPageIndex] = useSkipper() - - const table = useReactTable({ - data, - columns, - defaultColumn, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getPaginationRowModel: getPaginationRowModel(), - autoResetPageIndex, - // Provide our updateData function to our table meta - meta: { - updateData: (rowIndex, columnId, value) => { - // Skip page index reset until after next rerender - skipAutoResetPageIndex() - setData(old => - old.map((row, index) => { - if (index === rowIndex) { - return { - ...old[rowIndex]!, - [columnId]: value, - } - } - return row - }) - ) - }, - }, - debugTable: true, - }) - - return ( -
-
- - - {table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(header => { - return ( - - ) - })} - - ))} - - - {table.getRowModel().rows.map(row => { - return ( - - {row.getVisibleCells().map(cell => { - return ( - - ) - })} - - ) - })} - -
- {header.isPlaceholder ? null : ( -
- {flexRender( - header.column.columnDef.header, - header.getContext() - )} - {header.column.getCanFilter() ? ( -
- -
- ) : null} -
- )} -
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} -
-
-
- - - - - -
Page
- - {table.getState().pagination.pageIndex + 1} of{' '} - {table.getPageCount()} - -
- - | Go to page: - { - const page = e.target.value ? Number(e.target.value) - 1 : 0 - table.setPageIndex(page) - }} - className="border p-1 rounded w-16" - /> - - -
-
{table.getRowModel().rows.length} Rows
-
- -
-
- -
-
- ) -} -function Filter({ - column, - table, -}: { - column: Column - table: Table -}) { - const firstValue = table - .getPreFilteredRowModel() - .flatRows[0]?.getValue(column.id) - - const columnFilterValue = column.getFilterValue() - - return typeof firstValue === 'number' ? ( -
- - column.setFilterValue((old: [number, number]) => [ - e.target.value, - old?.[1], - ]) - } - placeholder={`Min`} - className="w-24 border shadow rounded" - /> - - column.setFilterValue((old: [number, number]) => [ - old?.[0], - e.target.value, - ]) - } - placeholder={`Max`} - className="w-24 border shadow rounded" - /> -
- ) : ( - column.setFilterValue(e.target.value)} - placeholder={`Search...`} - className="w-36 border shadow rounded" - /> - ) -} - -const rootElement = document.getElementById('root') -if (!rootElement) throw new Error('Failed to find the root element') - -ReactDOM.createRoot(rootElement).render( - - - -) diff --git a/examples/react/editable-data/src/makeData.ts b/examples/react/editable-data/src/makeData.ts deleted file mode 100644 index 331dd1eb19..0000000000 --- a/examples/react/editable-data/src/makeData.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { faker } from '@faker-js/faker' - -export type Person = { - firstName: string - lastName: string - age: number - visits: number - progress: number - status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] -} - -const range = (len: number) => { - const arr: number[] = [] - for (let i = 0; i < len; i++) { - arr.push(i) - } - return arr -} - -const newPerson = (): Person => { - return { - firstName: faker.person.firstName(), - lastName: faker.person.lastName(), - age: faker.number.int(40), - visits: faker.number.int(1000), - progress: faker.number.int(100), - status: faker.helpers.shuffle([ - 'relationship', - 'complicated', - 'single', - ])[0]!, - } -} - -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! - return range(len).map((d): Person => { - return { - ...newPerson(), - subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, - } - }) - } - - return makeDataLevel() -} diff --git a/examples/react/editable-data/tsconfig.json b/examples/react/editable-data/tsconfig.json deleted file mode 100644 index 6d545f543f..0000000000 --- a/examples/react/editable-data/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"] -} diff --git a/examples/react/editable-data/vite.config.js b/examples/react/editable-data/vite.config.js deleted file mode 100644 index 2e1361723a..0000000000 --- a/examples/react/editable-data/vite.config.js +++ /dev/null @@ -1,17 +0,0 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' -import rollupReplace from '@rollup/plugin-replace' - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [ - rollupReplace({ - preventAssignment: true, - values: { - __DEV__: JSON.stringify(true), - 'process.env.NODE_ENV': JSON.stringify('development'), - }, - }), - react(), - ], -}) diff --git a/examples/react/expanding/index.html b/examples/react/expanding/index.html index 3fc40c9367..1551290f72 100644 --- a/examples/react/expanding/index.html +++ b/examples/react/expanding/index.html @@ -4,7 +4,7 @@ Vite App - +
diff --git a/examples/react/expanding/package.json b/examples/react/expanding/package.json index a05a90b217..b255fc2bf2 100644 --- a/examples/react/expanding/package.json +++ b/examples/react/expanding/package.json @@ -1,25 +1,29 @@ { - "name": "tanstack-table-example-expanding", - "version": "0.0.0", + "name": "tanstack-react-table-example-expanding", "private": true, "scripts": { "dev": "vite", "build": "vite build", "serve": "vite preview", - "start": "vite" + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" }, "dependencies": { - "@faker-js/faker": "^8.4.1", - "@tanstack/react-table": "^8.20.6", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "@faker-js/faker": "^10.4.0", + "@tanstack/react-pacer": "^0.22.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6" }, "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/react/expanding/src/index.css b/examples/react/expanding/src/index.css index 43c09e0f6b..b9af6ded32 100644 --- a/examples/react/expanding/src/index.css +++ b/examples/react/expanding/src/index.css @@ -5,6 +5,8 @@ html { table { border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; } tbody { @@ -24,3 +26,340 @@ tfoot { tfoot th { font-weight: normal; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/expanding/src/main.tsx b/examples/react/expanding/src/main.tsx index 0b1bcad384..c395eadc1d 100644 --- a/examples/react/expanding/src/main.tsx +++ b/examples/react/expanding/src/main.tsx @@ -1,153 +1,157 @@ -import React, { HTMLProps } from 'react' +import React from 'react' import ReactDOM from 'react-dom/client' - -import './index.css' - import { - Column, - Table, - ExpandedState, - useReactTable, - getCoreRowModel, - getPaginationRowModel, - getFilteredRowModel, - getExpandedRowModel, - ColumnDef, - flexRender, + columnFilteringFeature, + createColumnHelper, + createExpandedRowModel, + createFilteredRowModel, + createPaginatedRowModel, + createSortedRowModel, + filterFns, + rowExpandingFeature, + rowPaginationFeature, + rowSelectionFeature, + rowSortingFeature, + sortFns, + tableFeatures, + useTable, } from '@tanstack/react-table' -import { makeData, Person } from './makeData' +import { useDebouncedCallback } from '@tanstack/react-pacer/debouncer' +import { makeData } from './makeData' +import type { HTMLProps } from 'react' +import type { Person } from './makeData' +import type { Column, Table } from '@tanstack/react-table' +import './index.css' + +const _features = tableFeatures({ + columnFilteringFeature, + rowExpandingFeature, + rowPaginationFeature, + rowSortingFeature, + rowSelectionFeature, +}) + +const columnHelper = createColumnHelper() function App() { const rerender = React.useReducer(() => ({}), {})[1] - const columns = React.useMemo[]>( - () => [ - { - accessorKey: 'firstName', - header: ({ table }) => ( - <> - {' '} - {' '} - First Name - - ), - cell: ({ row, getValue }) => ( -
-
+ const columns = React.useMemo( + () => + columnHelper.columns([ + columnHelper.accessor('firstName', { + header: ({ table }) => ( + <> {' '} - {row.getCanExpand() ? ( - - ) : ( - '🔵' - )}{' '} - {getValue()} + {' '} + First Name + + ), + cell: ({ row, getValue }) => ( +
+
+ {' '} + {row.getCanExpand() ? ( + + ) : ( + '🔵' + )}{' '} + {getValue()} +
-
- ), - footer: props => props.column.id, - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - cell: info => info.getValue(), - header: () => Last Name, - footer: props => props.column.id, - }, - { - accessorKey: 'age', - header: () => 'Age', - footer: props => props.column.id, - }, - { - accessorKey: 'visits', - header: () => Visits, - footer: props => props.column.id, - }, - { - accessorKey: 'status', - header: 'Status', - footer: props => props.column.id, - }, - { - accessorKey: 'progress', - header: 'Profile Progress', - footer: props => props.column.id, - }, - ], - [] + ), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }), + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + filterFn: 'between', + }), + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), + ]), + [], ) const [data, setData] = React.useState(() => makeData(100, 5, 3)) - const refreshData = () => setData(() => makeData(100, 5, 3)) - - const [expanded, setExpanded] = React.useState({}) + const refreshData = () => setData(makeData(100, 5, 3)) + const stressTest = () => setData(makeData(10_000, 5, 3)) - const table = useReactTable({ - data, - columns, - state: { - expanded, + const table = useTable( + { + _features, + _rowModels: { + expandedRowModel: createExpandedRowModel(), + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, + columns, + data, + getSubRows: (row) => row.subRows, + // filterFromLeafRows: true, + // maxLeafRowFilterDepth: 0, + debugTable: true, }, - onExpandedChange: setExpanded, - getSubRows: row => row.subRows, - getCoreRowModel: getCoreRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getExpandedRowModel: getExpandedRowModel(), - // filterFromLeafRows: true, - // maxLeafRowFilterDepth: 0, - debugTable: true, - }) + (state) => state, // default selector + ) return ( -
-
+
+
+ + +
+
- {table.getHeaderGroups().map(headerGroup => ( + {table.getHeaderGroups().map((headerGroup) => ( - {headerGroup.headers.map(header => { + {headerGroup.headers.map((header) => { return ( - {table.getRowModel().rows.map(row => { + {table.getRowModel().rows.map((row) => { return ( - {row.getVisibleCells().map(cell => { + {row.getAllCells().map((cell) => { return ( ) })} @@ -180,81 +181,75 @@ function App() { })}
{header.isPlaceholder ? null : (
- {flexRender( - header.column.columnDef.header, - header.getContext() - )} + {header.column.getCanFilter() ? (
@@ -162,16 +166,13 @@ function App() { ))}
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} +
-
-
+
+
- +
Page
- {table.getState().pagination.pageIndex + 1} of{' '} - {table.getPageCount()} + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()}
- + | Go to page: { + defaultValue={table.state.pagination.pageIndex + 1} + onChange={(e) => { const page = e.target.value ? Number(e.target.value) - 1 : 0 table.setPageIndex(page) }} - className="border p-1 rounded w-16" + className="page-size-input" />
-
{table.getRowModel().rows.length} Rows
+
{table.getRowModel().rows.length.toLocaleString()} Rows
-
- -
- -
{JSON.stringify(expanded, null, 2)}
- -
{JSON.stringify(table.getState().rowSelection, null, 2)}
+
{JSON.stringify(table.state, null, 2)}
) } @@ -263,8 +258,8 @@ function Filter({ column, table, }: { - column: Column - table: Table + column: Column + table: Table }) { const firstValue = table .getPreFilteredRowModel() @@ -273,39 +268,70 @@ function Filter({ const columnFilterValue = column.getFilterValue() return typeof firstValue === 'number' ? ( -
- + - column.setFilterValue((old: [number, number]) => [ - e.target.value, + value={(columnFilterValue as [number, number] | undefined)?.[0] ?? ''} + onChange={(value) => + column.setFilterValue((old: [number, number] | undefined) => [ + value, old?.[1], ]) } placeholder={`Min`} - className="w-24 border shadow rounded" + className="filter-input" /> - - column.setFilterValue((old: [number, number]) => [ + value={(columnFilterValue as [number, number] | undefined)?.[1] ?? ''} + onChange={(value) => + column.setFilterValue((old: [number, number] | undefined) => [ old?.[0], - e.target.value, + value, ]) } placeholder={`Max`} - className="w-24 border shadow rounded" + className="filter-input" />
) : ( - column.setFilterValue(e.target.value)} + onChange={(value) => column.setFilterValue(value)} placeholder={`Search...`} - className="w-36 border shadow rounded" + className="filter-select" + /> + ) +} + +// A debounced input react component +function DebouncedInput({ + value: initialValue, + onChange, + debounce = 500, + ...props +}: { + value: string | number + onChange: (value: string | number) => void + debounce?: number +} & Omit, 'onChange'>) { + const [value, setValue] = React.useState(initialValue) + + React.useEffect(() => { + setValue(initialValue) + }, [initialValue]) + + const debouncedOnChange = useDebouncedCallback(onChange, { wait: debounce }) + + return ( + { + setValue(e.target.value) + debouncedOnChange(e.target.value) + }} /> ) } @@ -327,7 +353,7 @@ function IndeterminateCheckbox({ ) @@ -339,5 +365,5 @@ if (!rootElement) throw new Error('Failed to find the root element') ReactDOM.createRoot(rootElement).render( - + , ) diff --git a/examples/react/expanding/src/makeData.ts b/examples/react/expanding/src/makeData.ts index 331dd1eb19..95302cdf16 100644 --- a/examples/react/expanding/src/makeData.ts +++ b/examples/react/expanding/src/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,14 +29,14 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! - return range(len).map((d): Person => { +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_): Person => { return { ...newPerson(), subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, diff --git a/examples/react/expanding/src/vite-env.d.ts b/examples/react/expanding/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/expanding/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/expanding/tsconfig.json b/examples/react/expanding/tsconfig.json index 6d545f543f..33a3e872be 100644 --- a/examples/react/expanding/tsconfig.json +++ b/examples/react/expanding/tsconfig.json @@ -5,20 +5,17 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, - "include": ["src"] + "include": ["src", "vite.config.js", "vite.config.ts"] } diff --git a/examples/react/expanding/vite.config.js b/examples/react/expanding/vite.config.js index 2e1361723a..1755f6bea8 100644 --- a/examples/react/expanding/vite.config.js +++ b/examples/react/expanding/vite.config.js @@ -1,5 +1,6 @@ import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' import rollupReplace from '@rollup/plugin-replace' // https://vitejs.dev/config/ @@ -13,5 +14,10 @@ export default defineConfig({ }, }), react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), ], }) diff --git a/examples/react/filters-faceted/index.html b/examples/react/filters-faceted/index.html index 3fc40c9367..1551290f72 100644 --- a/examples/react/filters-faceted/index.html +++ b/examples/react/filters-faceted/index.html @@ -4,7 +4,7 @@ Vite App - +
diff --git a/examples/react/filters-faceted/package.json b/examples/react/filters-faceted/package.json index 7749a6f20a..fe80116f40 100644 --- a/examples/react/filters-faceted/package.json +++ b/examples/react/filters-faceted/package.json @@ -1,26 +1,30 @@ { - "name": "tanstack-table-example-filters-faceted", - "version": "0.0.0", + "name": "tanstack-react-table-example-filters-faceted", "private": true, "scripts": { "dev": "vite", "build": "vite build", - "serve": "vite preview --port 3001", - "start": "vite" + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" }, "dependencies": { - "@faker-js/faker": "^8.4.1", - "@tanstack/match-sorter-utils": "^8.19.4", - "@tanstack/react-table": "^8.20.6", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "@faker-js/faker": "^10.4.0", + "@tanstack/match-sorter-utils": "^9.0.0-alpha.4", + "@tanstack/react-pacer": "^0.22.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6" }, "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/react/filters-faceted/src/index.css b/examples/react/filters-faceted/src/index.css index 43c09e0f6b..b9af6ded32 100644 --- a/examples/react/filters-faceted/src/index.css +++ b/examples/react/filters-faceted/src/index.css @@ -5,6 +5,8 @@ html { table { border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; } tbody { @@ -24,3 +26,340 @@ tfoot { tfoot th { font-weight: normal; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/filters-faceted/src/main.tsx b/examples/react/filters-faceted/src/main.tsx index 76ae112325..256f9e79f6 100644 --- a/examples/react/filters-faceted/src/main.tsx +++ b/examples/react/filters-faceted/src/main.tsx @@ -1,133 +1,130 @@ import React from 'react' import ReactDOM from 'react-dom/client' - import './index.css' - import { + columnFacetingFeature, + columnFilteringFeature, + createColumnHelper, + createFacetedMinMaxValues, + createFacetedRowModel, + createFacetedUniqueValues, + createFilteredRowModel, + createPaginatedRowModel, + filterFns, + rowPaginationFeature, + tableFeatures, + useTable, +} from '@tanstack/react-table' +import { useDebouncedCallback } from '@tanstack/react-pacer/debouncer' +import { makeData } from './makeData' +import type { + CellData, Column, - ColumnDef, - ColumnFiltersState, RowData, - flexRender, - getCoreRowModel, - getFacetedMinMaxValues, - getFacetedRowModel, - getFacetedUniqueValues, - getFilteredRowModel, - getPaginationRowModel, - getSortedRowModel, - useReactTable, + TableFeatures, } from '@tanstack/react-table' +import type { Person } from './makeData' -import { makeData, Person } from './makeData' +const _features = tableFeatures({ + columnFacetingFeature, + columnFilteringFeature, + rowPaginationFeature, +}) + +const columnHelper = createColumnHelper() declare module '@tanstack/react-table' { - //allows us to define custom properties for our columns - interface ColumnMeta { + // allows us to define custom properties for our columns + interface ColumnMeta< + TFeatures extends TableFeatures, + TData extends RowData, + TValue extends CellData = CellData, + > { filterVariant?: 'text' | 'range' | 'select' } } function App() { - const rerender = React.useReducer(() => ({}), {})[1] - - const [columnFilters, setColumnFilters] = React.useState( - [] - ) - - const columns = React.useMemo[]>( - () => [ - { - accessorKey: 'firstName', - cell: info => info.getValue(), - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - cell: info => info.getValue(), - header: () => Last Name, - }, - { - accessorKey: 'age', - header: () => 'Age', - meta: { - filterVariant: 'range', - }, - }, - { - accessorKey: 'visits', - header: () => Visits, - meta: { - filterVariant: 'range', - }, - }, - { - accessorKey: 'status', - header: 'Status', - meta: { - filterVariant: 'select', - }, - }, - { - accessorKey: 'progress', - header: 'Profile Progress', - meta: { - filterVariant: 'range', - }, - }, - ], - [] + const columns = React.useMemo( + () => + columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + }), + columnHelper.accessor('age', { + header: () => 'Age', + meta: { + filterVariant: 'range', + }, + }), + columnHelper.accessor('visits', { + header: () => Visits, + meta: { + filterVariant: 'range', + }, + }), + columnHelper.accessor('status', { + header: 'Status', + meta: { + filterVariant: 'select', + }, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + meta: { + filterVariant: 'range', + }, + }), + ]), + [], ) - const [data, setData] = React.useState(() => makeData(5_000)) - const refreshData = () => setData(_old => makeData(100_000)) //stress test + const [data, setData] = React.useState>(() => makeData(5_000)) + const refreshData = () => setData(makeData(5_000)) + const stressTest = () => setData(makeData(200_000)) + const rerender = React.useReducer(() => ({}), {})[1] - const table = useReactTable({ - data, - columns, - state: { - columnFilters, + const table = useTable( + { + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), // client-side filtering + paginatedRowModel: createPaginatedRowModel(), + facetedRowModel: createFacetedRowModel(), // client-side faceting + facetedMinMaxValues: createFacetedMinMaxValues(), // generate min/max values for range filter + facetedUniqueValues: createFacetedUniqueValues(), // generate unique values for select filter/autocomplete + }, + columns, + data, + debugTable: true, + debugHeaders: true, + debugColumns: false, }, - onColumnFiltersChange: setColumnFilters, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), //client-side filtering - getSortedRowModel: getSortedRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getFacetedRowModel: getFacetedRowModel(), // client-side faceting - getFacetedUniqueValues: getFacetedUniqueValues(), // generate unique values for select filter/autocomplete - getFacetedMinMaxValues: getFacetedMinMaxValues(), // generate min/max values for range filter - debugTable: true, - debugHeaders: true, - debugColumns: false, - }) + (state) => state, // default selector + ) return ( -
+
+
+ + +
- {table.getHeaderGroups().map(headerGroup => ( + {table.getHeaderGroups().map((headerGroup) => ( - {headerGroup.headers.map(header => { + {headerGroup.headers.map((header) => { return ( - {table.getRowModel().rows.map(row => { + {table.getRowModel().rows.map((row) => { return ( - {row.getVisibleCells().map(cell => { + {row.getAllCells().map((cell) => { return ( ) })} @@ -161,91 +155,86 @@ function App() { })}
{header.isPlaceholder ? null : ( <> -
- {flexRender( - header.column.columnDef.header, - header.getContext() - )} - {{ - asc: ' 🔼', - desc: ' 🔽', - }[header.column.getIsSorted() as string] ?? null} +
+
{header.column.getCanFilter() ? (
@@ -143,16 +140,13 @@ function App() { ))}
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} +
-
-
+
+
- +
Page
- {table.getState().pagination.pageIndex + 1} of{' '} - {table.getPageCount()} + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()}
- + | Go to page: { + defaultValue={table.state.pagination.pageIndex + 1} + onChange={(e) => { const page = e.target.value ? Number(e.target.value) - 1 : 0 table.setPageIndex(page) }} - className="border p-1 rounded w-16" + className="page-size-input" />
-
{table.getPrePaginationRowModel().rows.length} Rows
- + {table.getPrePaginatedRowModel().rows.length.toLocaleString()} Rows
- +
-
-        {JSON.stringify(
-          { columnFilters: table.getState().columnFilters },
-          null,
-          2
-        )}
-      
+
{JSON.stringify(table.state, null, 2)}
) } -function Filter({ column }: { column: Column }) { +function Filter({ column }: { column: Column }) { const { filterVariant } = column.columnDef.meta ?? {} const columnFilterValue = column.getFilterValue() + const minMaxValues = column.getFacetedMinMaxValues() + const sortedUniqueValues = React.useMemo( () => filterVariant === 'range' @@ -253,53 +242,53 @@ function Filter({ column }: { column: Column }) { : Array.from(column.getFacetedUniqueValues().keys()) .sort() .slice(0, 5000), - [column.getFacetedUniqueValues(), filterVariant] + [column.getFacetedUniqueValues(), filterVariant], ) return filterVariant === 'range' ? (
-
+
- column.setFilterValue((old: [number, number]) => [value, old?.[1]]) + min={Number(minMaxValues?.[0] ?? '')} + max={Number(minMaxValues?.[1] ?? '')} + value={(columnFilterValue as [number, number] | undefined)?.[0] ?? ''} + onChange={(value) => + column.setFilterValue((old: [number, number] | undefined) => [ + value, + old?.[1], + ]) } placeholder={`Min ${ - column.getFacetedMinMaxValues()?.[0] !== undefined - ? `(${column.getFacetedMinMaxValues()?.[0]})` - : '' + minMaxValues?.[0] !== undefined ? `(${minMaxValues[0]})` : '' }`} - className="w-24 border shadow rounded" + className="filter-input" /> - column.setFilterValue((old: [number, number]) => [old?.[0], value]) + min={Number(minMaxValues?.[0] ?? '')} + max={Number(minMaxValues?.[1] ?? '')} + value={(columnFilterValue as [number, number] | undefined)?.[1] ?? ''} + onChange={(value) => + column.setFilterValue((old: [number, number] | undefined) => [ + old?.[0], + value, + ]) } - placeholder={`Max ${ - column.getFacetedMinMaxValues()?.[1] - ? `(${column.getFacetedMinMaxValues()?.[1]})` - : '' - }`} - className="w-24 border shadow rounded" + placeholder={`Max ${minMaxValues?.[1] ? `(${minMaxValues[1]})` : ''}`} + className="filter-input" />
-
+
) : filterVariant === 'select' ? ( setValue(e.target.value)} /> + { + setValue(e.target.value) + debouncedOnChange(e.target.value) + }} + /> ) } @@ -362,5 +352,5 @@ if (!rootElement) throw new Error('Failed to find the root element') ReactDOM.createRoot(rootElement).render( - + , ) diff --git a/examples/react/filters-faceted/src/makeData.ts b/examples/react/filters-faceted/src/makeData.ts index 331dd1eb19..b9055b2d8c 100644 --- a/examples/react/filters-faceted/src/makeData.ts +++ b/examples/react/filters-faceted/src/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,14 +29,14 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! - return range(len).map((d): Person => { +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { return { ...newPerson(), subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, diff --git a/examples/react/filters-faceted/src/vite-env.d.ts b/examples/react/filters-faceted/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/filters-faceted/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/filters-faceted/tsconfig.json b/examples/react/filters-faceted/tsconfig.json index 6d545f543f..33a3e872be 100644 --- a/examples/react/filters-faceted/tsconfig.json +++ b/examples/react/filters-faceted/tsconfig.json @@ -5,20 +5,17 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, - "include": ["src"] + "include": ["src", "vite.config.js", "vite.config.ts"] } diff --git a/examples/react/filters-faceted/vite.config.js b/examples/react/filters-faceted/vite.config.js index 2e1361723a..1755f6bea8 100644 --- a/examples/react/filters-faceted/vite.config.js +++ b/examples/react/filters-faceted/vite.config.js @@ -1,5 +1,6 @@ import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' import rollupReplace from '@rollup/plugin-replace' // https://vitejs.dev/config/ @@ -13,5 +14,10 @@ export default defineConfig({ }, }), react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), ], }) diff --git a/examples/react/filters-fuzzy/index.html b/examples/react/filters-fuzzy/index.html index 3fc40c9367..1551290f72 100644 --- a/examples/react/filters-fuzzy/index.html +++ b/examples/react/filters-fuzzy/index.html @@ -4,7 +4,7 @@ Vite App - +
diff --git a/examples/react/filters-fuzzy/package.json b/examples/react/filters-fuzzy/package.json index 4fd5f34893..4429dd0c7a 100644 --- a/examples/react/filters-fuzzy/package.json +++ b/examples/react/filters-fuzzy/package.json @@ -1,26 +1,30 @@ { - "name": "tanstack-table-example-filters-fuzzy", - "version": "0.0.0", + "name": "tanstack-react-table-example-filters-fuzzy", "private": true, "scripts": { "dev": "vite", "build": "vite build", - "serve": "vite preview --port 3001", - "start": "vite" + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" }, "dependencies": { - "@faker-js/faker": "^8.4.1", - "@tanstack/match-sorter-utils": "^8.19.4", - "@tanstack/react-table": "^8.20.6", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "@faker-js/faker": "^10.4.0", + "@tanstack/match-sorter-utils": "^9.0.0-alpha.4", + "@tanstack/react-pacer": "^0.22.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6" }, "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/react/filters-fuzzy/src/index.css b/examples/react/filters-fuzzy/src/index.css index 43c09e0f6b..b9af6ded32 100644 --- a/examples/react/filters-fuzzy/src/index.css +++ b/examples/react/filters-fuzzy/src/index.css @@ -5,6 +5,8 @@ html { table { border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; } tbody { @@ -24,3 +26,340 @@ tfoot { tfoot th { font-weight: normal; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/filters-fuzzy/src/main.tsx b/examples/react/filters-fuzzy/src/main.tsx index f76d76c8f3..d4a20e3d93 100644 --- a/examples/react/filters-fuzzy/src/main.tsx +++ b/examples/react/filters-fuzzy/src/main.tsx @@ -1,49 +1,50 @@ import React from 'react' import ReactDOM from 'react-dom/client' - import './index.css' - import { - Column, - ColumnDef, - ColumnFiltersState, - FilterFn, - SortingFn, - flexRender, - getCoreRowModel, - getFilteredRowModel, - getPaginationRowModel, - getSortedRowModel, - sortingFns, - useReactTable, + columnFilteringFeature, + createColumnHelper, + createFilteredRowModel, + createPaginatedRowModel, + createSortedRowModel, + filterFns, + globalFilteringFeature, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, + useTable, } from '@tanstack/react-table' +import { useDebouncedCallback } from '@tanstack/react-pacer/debouncer' +import { compareItems, rankItem } from '@tanstack/match-sorter-utils' +import { makeData } from './makeData' +import type { Person } from './makeData' +import type { Column, FilterFn, SortFn } from '@tanstack/react-table' // A TanStack fork of Kent C. Dodds' match-sorter library that provides ranking information -import { - RankingInfo, - rankItem, - compareItems, -} from '@tanstack/match-sorter-utils' +import type { RankingInfo } from '@tanstack/match-sorter-utils' -import { makeData, Person } from './makeData' +const _features = tableFeatures({ + columnFilteringFeature, + globalFilteringFeature, + rowSortingFeature, + rowPaginationFeature, +}) -declare module '@tanstack/react-table' { - //add fuzzy filter to the filterFns - interface FilterFns { - fuzzy: FilterFn - } - interface FilterMeta { - itemRank: RankingInfo - } -} +const columnHelper = createColumnHelper() // Define a custom fuzzy filter function that will apply ranking info to rows (using match-sorter utils) -const fuzzyFilter: FilterFn = (row, columnId, value, addMeta) => { +const fuzzyFilter: FilterFn = ( + row, + columnId, + value, + addMeta, +) => { // Rank the item const itemRank = rankItem(row.getValue(columnId), value) // Store the itemRank info - addMeta({ + addMeta?.({ itemRank, }) @@ -52,126 +53,128 @@ const fuzzyFilter: FilterFn = (row, columnId, value, addMeta) => { } // Define a custom fuzzy sort function that will sort by rank if the row has ranking information -const fuzzySort: SortingFn = (rowA, rowB, columnId) => { +const fuzzySort: SortFn = (rowA, rowB, columnId) => { let dir = 0 // Only sort by rank if the column has ranking information + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (rowA.columnFiltersMeta[columnId]) { dir = compareItems( - rowA.columnFiltersMeta[columnId]?.itemRank!, - rowB.columnFiltersMeta[columnId]?.itemRank! + rowA.columnFiltersMeta[columnId].itemRank!, + rowB.columnFiltersMeta[columnId].itemRank!, ) } // Provide an alphanumeric fallback for when the item ranks are equal - return dir === 0 ? sortingFns.alphanumeric(rowA, rowB, columnId) : dir + return dir === 0 ? sortFns.alphanumeric(rowA, rowB, columnId) : dir +} + +declare module '@tanstack/react-table' { + // add fuzzy filter to the filterFns + interface FilterFns { + fuzzy: FilterFn + } + interface FilterMeta { + itemRank?: RankingInfo + } } function App() { const rerender = React.useReducer(() => ({}), {})[1] - const [columnFilters, setColumnFilters] = React.useState( - [] + const columns = React.useMemo( + () => + columnHelper.columns([ + columnHelper.accessor('id', { + filterFn: 'equalsString', // note: normal non-fuzzy filter column - exact match required + }), + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + filterFn: 'includesStringSensitive', // note: normal non-fuzzy filter column - case sensitive + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + filterFn: 'includesString', // note: normal non-fuzzy filter column - case insensitive + }), + columnHelper.accessor((row) => `${row.firstName} ${row.lastName}`, { + id: 'fullName', + header: 'Full Name', + cell: (info) => info.getValue(), + filterFn: 'fuzzy', // using our custom fuzzy filter function + // filterFn: fuzzyFilter, //or just define with the function + sortFn: fuzzySort, // sort by fuzzy rank (falls back to alphanumeric) + }), + ]), + [], ) - const [globalFilter, setGlobalFilter] = React.useState('') - const columns = React.useMemo[]>( - () => [ - { - accessorKey: 'id', - filterFn: 'equalsString', //note: normal non-fuzzy filter column - exact match required - }, - { - accessorKey: 'firstName', - cell: info => info.getValue(), - filterFn: 'includesStringSensitive', //note: normal non-fuzzy filter column - case sensitive - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - cell: info => info.getValue(), - header: () => Last Name, - filterFn: 'includesString', //note: normal non-fuzzy filter column - case insensitive - }, - { - accessorFn: row => `${row.firstName} ${row.lastName}`, - id: 'fullName', - header: 'Full Name', - cell: info => info.getValue(), - filterFn: 'fuzzy', //using our custom fuzzy filter function - // filterFn: fuzzyFilter, //or just define with the function - sortingFn: fuzzySort, //sort by fuzzy rank (falls back to alphanumeric) - }, - ], - [] - ) + const [data, setData] = React.useState>(() => makeData(5_000)) + const refreshData = () => setData(makeData(5_000)) + const stressTest = () => setData(makeData(200_000)) - const [data, setData] = React.useState(() => makeData(5_000)) - const refreshData = () => setData(_old => makeData(50_000)) //stress test - - const table = useReactTable({ - data, - columns, - filterFns: { - fuzzy: fuzzyFilter, //define as a filter function that can be used in column definitions - }, - state: { - columnFilters, - globalFilter, + const table = useTable( + { + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel({ + ...filterFns, + fuzzy: fuzzyFilter, + }), + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, + columns, + data, + globalFilterFn: 'fuzzy', // apply fuzzy filter to the global filter (most common use case for fuzzy filter) + debugTable: true, + debugHeaders: true, + debugColumns: false, }, - onColumnFiltersChange: setColumnFilters, - onGlobalFilterChange: setGlobalFilter, - globalFilterFn: 'fuzzy', //apply fuzzy filter to the global filter (most common use case for fuzzy filter) - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), //client side filtering - getSortedRowModel: getSortedRowModel(), - getPaginationRowModel: getPaginationRowModel(), - debugTable: true, - debugHeaders: true, - debugColumns: false, - }) + (state) => state, // default selector + ) - //apply the fuzzy sort if the fullName column is being filtered + // apply the fuzzy sort if the fullName column is being filtered React.useEffect(() => { - if (table.getState().columnFilters[0]?.id === 'fullName') { - if (table.getState().sorting[0]?.id !== 'fullName') { + if (table.store.state.columnFilters[0]?.id === 'fullName') { + if (table.store.state.sorting[0]?.id !== 'fullName') { table.setSorting([{ id: 'fullName', desc: false }]) } } - }, [table.getState().columnFilters[0]?.id]) + }, [table.store.state.columnFilters[0]?.id]) return ( -
+
+
+ + +
setGlobalFilter(String(value))} - className="p-2 font-lg shadow border border-block" + value={table.state.globalFilter ?? ''} + onChange={(value) => table.setGlobalFilter(String(value))} + className="summary-panel" placeholder="Search all columns..." />
-
+
- {table.getHeaderGroups().map(headerGroup => ( + {table.getHeaderGroups().map((headerGroup) => ( - {headerGroup.headers.map(header => { + {headerGroup.headers.map((header) => { return ( - {table.getRowModel().rows.map(row => { + {table.getRowModel().rows.map((row) => { return ( - {row.getVisibleCells().map(cell => { + {row.getAllCells().map((cell) => { return ( ) })} @@ -209,99 +209,89 @@ function App() { })}
{header.isPlaceholder ? null : ( <>
- {flexRender( - header.column.columnDef.header, - header.getContext() - )} + {{ asc: ' 🔼', desc: ' 🔽', @@ -191,16 +194,13 @@ function App() { ))}
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} +
-
-
+
+
- +
Page
- {table.getState().pagination.pageIndex + 1} of{' '} - {table.getPageCount()} + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()}
- + | Go to page: { + defaultValue={table.state.pagination.pageIndex + 1} + onChange={(e) => { const page = e.target.value ? Number(e.target.value) - 1 : 0 table.setPageIndex(page) }} - className="border p-1 rounded w-16" + className="page-size-input" />
-
{table.getPrePaginationRowModel().rows.length} Rows
- + {table.getPrePaginatedRowModel().rows.length.toLocaleString()} Rows
- +
-
-        {JSON.stringify(
-          {
-            columnFilters: table.getState().columnFilters,
-            globalFilter: table.getState().globalFilter,
-          },
-          null,
-          2
-        )}
-      
+
{JSON.stringify(table.state, null, 2)}
) } -function Filter({ column }: { column: Column }) { +function Filter({ column }: { column: Column }) { const columnFilterValue = column.getFilterValue() return ( column.setFilterValue(value)} + onChange={(value) => column.setFilterValue(value)} placeholder={`Search...`} - className="w-36 border shadow rounded" + className="filter-select" /> ) } @@ -323,16 +313,17 @@ function DebouncedInput({ setValue(initialValue) }, [initialValue]) - React.useEffect(() => { - const timeout = setTimeout(() => { - onChange(value) - }, debounce) - - return () => clearTimeout(timeout) - }, [value]) + const debouncedOnChange = useDebouncedCallback(onChange, { wait: debounce }) return ( - setValue(e.target.value)} /> + { + setValue(e.target.value) + debouncedOnChange(e.target.value) + }} + /> ) } @@ -342,5 +333,5 @@ if (!rootElement) throw new Error('Failed to find the root element') ReactDOM.createRoot(rootElement).render( - + , ) diff --git a/examples/react/filters-fuzzy/src/makeData.ts b/examples/react/filters-fuzzy/src/makeData.ts index 6d4d5076a0..8d13e339f5 100644 --- a/examples/react/filters-fuzzy/src/makeData.ts +++ b/examples/react/filters-fuzzy/src/makeData.ts @@ -8,11 +8,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -31,13 +31,13 @@ const newPerson = (num: number): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] return range(len).map((index): Person => { return { ...newPerson(index), diff --git a/examples/react/filters-fuzzy/src/vite-env.d.ts b/examples/react/filters-fuzzy/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/filters-fuzzy/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/filters-fuzzy/tsconfig.json b/examples/react/filters-fuzzy/tsconfig.json index 6d545f543f..33a3e872be 100644 --- a/examples/react/filters-fuzzy/tsconfig.json +++ b/examples/react/filters-fuzzy/tsconfig.json @@ -5,20 +5,17 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, - "include": ["src"] + "include": ["src", "vite.config.js", "vite.config.ts"] } diff --git a/examples/react/filters-fuzzy/vite.config.js b/examples/react/filters-fuzzy/vite.config.js index 2e1361723a..1755f6bea8 100644 --- a/examples/react/filters-fuzzy/vite.config.js +++ b/examples/react/filters-fuzzy/vite.config.js @@ -1,5 +1,6 @@ import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' import rollupReplace from '@rollup/plugin-replace' // https://vitejs.dev/config/ @@ -13,5 +14,10 @@ export default defineConfig({ }, }), react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), ], }) diff --git a/examples/react/filters/index.html b/examples/react/filters/index.html index 3fc40c9367..1551290f72 100644 --- a/examples/react/filters/index.html +++ b/examples/react/filters/index.html @@ -4,7 +4,7 @@ Vite App - +
diff --git a/examples/react/filters/package.json b/examples/react/filters/package.json index 055342c02f..64129187b0 100644 --- a/examples/react/filters/package.json +++ b/examples/react/filters/package.json @@ -1,26 +1,30 @@ { - "name": "tanstack-table-example-filters", - "version": "0.0.0", + "name": "tanstack-react-table-example-filters", "private": true, "scripts": { "dev": "vite", "build": "vite build", - "serve": "vite preview --port 3001", - "start": "vite" + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" }, "dependencies": { - "@faker-js/faker": "^8.4.1", - "@tanstack/match-sorter-utils": "^8.19.4", - "@tanstack/react-table": "^8.20.6", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "@faker-js/faker": "^10.4.0", + "@tanstack/match-sorter-utils": "^9.0.0-alpha.4", + "@tanstack/react-pacer": "^0.22.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6" }, "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/react/filters/src/index.css b/examples/react/filters/src/index.css index 43c09e0f6b..b9af6ded32 100644 --- a/examples/react/filters/src/index.css +++ b/examples/react/filters/src/index.css @@ -5,6 +5,8 @@ html { table { border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; } tbody { @@ -24,3 +26,340 @@ tfoot { tfoot th { font-weight: normal; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/filters/src/main.tsx b/examples/react/filters/src/main.tsx index 4837edbc18..d3c3e180e1 100644 --- a/examples/react/filters/src/main.tsx +++ b/examples/react/filters/src/main.tsx @@ -1,134 +1,127 @@ import React from 'react' import ReactDOM from 'react-dom/client' - import './index.css' - import { + columnFilteringFeature, + createColumnHelper, + createFilteredRowModel, + createPaginatedRowModel, + filterFns, + rowPaginationFeature, + tableFeatures, + useTable, +} from '@tanstack/react-table' +import { useDebouncedCallback } from '@tanstack/react-pacer/debouncer' +import { makeData } from './makeData' +import type { + CellData, Column, - ColumnDef, - ColumnFiltersState, RowData, - flexRender, - getCoreRowModel, - getFilteredRowModel, - getPaginationRowModel, - getSortedRowModel, - useReactTable, + TableFeatures, } from '@tanstack/react-table' - -import { makeData, Person } from './makeData' +import type { Person } from './makeData' declare module '@tanstack/react-table' { - //allows us to define custom properties for our columns - interface ColumnMeta { + // allows us to define custom properties for our columns + interface ColumnMeta< + TFeatures extends TableFeatures, + TData extends RowData, + TValue extends CellData = CellData, + > { filterVariant?: 'text' | 'range' | 'select' } } +const _features = tableFeatures({ + columnFilteringFeature, + rowPaginationFeature, +}) + +const columnHelper = createColumnHelper() + function App() { const rerender = React.useReducer(() => ({}), {})[1] - const [columnFilters, setColumnFilters] = React.useState( - [] + const columns = React.useMemo( + () => + columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + }), + columnHelper.accessor((row) => `${row.firstName} ${row.lastName}`, { + id: 'fullName', + header: 'Full Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('age', { + header: () => 'Age', + meta: { + filterVariant: 'range', + }, + }), + columnHelper.accessor('visits', { + header: () => Visits, + meta: { + filterVariant: 'range', + }, + }), + columnHelper.accessor('status', { + header: 'Status', + meta: { + filterVariant: 'select', + }, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + meta: { + filterVariant: 'range', + }, + }), + ]), + [], ) - const columns = React.useMemo[]>( - () => [ - { - accessorKey: 'firstName', - cell: info => info.getValue(), - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - cell: info => info.getValue(), - header: () => Last Name, - }, - { - accessorFn: row => `${row.firstName} ${row.lastName}`, - id: 'fullName', - header: 'Full Name', - cell: info => info.getValue(), - }, - { - accessorKey: 'age', - header: () => 'Age', - meta: { - filterVariant: 'range', - }, - }, - { - accessorKey: 'visits', - header: () => Visits, - meta: { - filterVariant: 'range', - }, - }, - { - accessorKey: 'status', - header: 'Status', - meta: { - filterVariant: 'select', - }, - }, - { - accessorKey: 'progress', - header: 'Profile Progress', - meta: { - filterVariant: 'range', - }, - }, - ], - [] - ) - - const [data, setData] = React.useState(() => makeData(5_000)) - const refreshData = () => setData(_old => makeData(50_000)) //stress test + const [data, setData] = React.useState>(() => makeData(5_000)) + const refreshData = () => setData(makeData(5_000)) + const stressTest = () => setData(makeData(200_000)) - const table = useReactTable({ - data, - columns, - filterFns: {}, - state: { - columnFilters, + const table = useTable( + { + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), // client side filtering + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data, + debugTable: true, + debugColumns: true, }, - onColumnFiltersChange: setColumnFilters, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), //client side filtering - getSortedRowModel: getSortedRowModel(), - getPaginationRowModel: getPaginationRowModel(), - debugTable: true, - debugHeaders: true, - debugColumns: false, - }) + (state) => state, // default selector + ) return ( -
+
+
+ + +
- {table.getHeaderGroups().map(headerGroup => ( + {table.getHeaderGroups().map((headerGroup) => ( - {headerGroup.headers.map(header => { + {headerGroup.headers.map((header) => { return ( - {table.getRowModel().rows.map(row => { + {table.getRowModel().rows.map((row) => { return ( - {row.getVisibleCells().map(cell => { + {row.getAllCells().map((cell) => { return ( ) })} @@ -162,120 +152,123 @@ function App() { })}
{header.isPlaceholder ? null : ( <> -
- {flexRender( - header.column.columnDef.header, - header.getContext() - )} - {{ - asc: ' 🔼', - desc: ' 🔽', - }[header.column.getIsSorted() as string] ?? null} +
+
{header.column.getCanFilter() ? (
@@ -144,16 +137,13 @@ function App() { ))}
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} +
-
-
+
+
- +
Page
- {table.getState().pagination.pageIndex + 1} of{' '} - {table.getPageCount()} + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()}
- + | Go to page: { + defaultValue={table.state.pagination.pageIndex + 1} + onChange={(e) => { const page = e.target.value ? Number(e.target.value) - 1 : 0 table.setPageIndex(page) }} - className="border p-1 rounded w-16" + className="page-size-input" />
-
{table.getPrePaginationRowModel().rows.length} Rows
- + {table.getPrePaginatedRowModel().rows.length.toLocaleString()} Rows
- +
-
-        {JSON.stringify(
-          { columnFilters: table.getState().columnFilters },
-          null,
-          2
-        )}
-      
+
{JSON.stringify(table.state, null, 2)}
) } -function Filter({ column }: { column: Column }) { +function Filter({ + column, +}: { + column: Column +}) { const columnFilterValue = column.getFilterValue() const { filterVariant } = column.columnDef.meta ?? {} return filterVariant === 'range' ? (
-
+
{/* See faceted column filters example for min max values functionality */} - column.setFilterValue((old: [number, number]) => [value, old?.[1]]) + value={(columnFilterValue as [number, number] | undefined)?.[0] ?? ''} + onChange={(value) => + column.setFilterValue((old: [number, number] | undefined) => [ + value, + old?.[1], + ]) } placeholder={`Min`} - className="w-24 border shadow rounded" + className="filter-input" /> - column.setFilterValue((old: [number, number]) => [old?.[0], value]) + value={(columnFilterValue as [number, number] | undefined)?.[1] ?? ''} + onChange={(value) => + column.setFilterValue((old: [number, number] | undefined) => [ + old?.[0], + value, + ]) } placeholder={`Max`} - className="w-24 border shadow rounded" + className="filter-input" />
-
+
) : filterVariant === 'select' ? ( ) : ( column.setFilterValue(value)} + className="filter-select" + onChange={(value) => column.setFilterValue(value)} placeholder={`Search...`} type="text" value={(columnFilterValue ?? '') as string} @@ -313,16 +306,17 @@ function DebouncedInput({ setValue(initialValue) }, [initialValue]) - React.useEffect(() => { - const timeout = setTimeout(() => { - onChange(value) - }, debounce) - - return () => clearTimeout(timeout) - }, [value]) + const debouncedOnChange = useDebouncedCallback(onChange, { wait: debounce }) return ( - setValue(e.target.value)} /> + { + setValue(e.target.value) + debouncedOnChange(e.target.value) + }} + /> ) } @@ -332,5 +326,5 @@ if (!rootElement) throw new Error('Failed to find the root element') ReactDOM.createRoot(rootElement).render( - + , ) diff --git a/examples/react/filters/src/makeData.ts b/examples/react/filters/src/makeData.ts index 331dd1eb19..b9055b2d8c 100644 --- a/examples/react/filters/src/makeData.ts +++ b/examples/react/filters/src/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,14 +29,14 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! - return range(len).map((d): Person => { +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { return { ...newPerson(), subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, diff --git a/examples/react/filters/src/vite-env.d.ts b/examples/react/filters/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/filters/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/filters/tsconfig.json b/examples/react/filters/tsconfig.json index 6d545f543f..840883b8ab 100644 --- a/examples/react/filters/tsconfig.json +++ b/examples/react/filters/tsconfig.json @@ -5,20 +5,18 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "noErrorTruncation": true, + "allowJs": true }, - "include": ["src"] + "include": ["src", "vite.config.js", "vite.config.ts"] } diff --git a/examples/react/filters/vite.config.js b/examples/react/filters/vite.config.js index 2e1361723a..1755f6bea8 100644 --- a/examples/react/filters/vite.config.js +++ b/examples/react/filters/vite.config.js @@ -1,5 +1,6 @@ import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' import rollupReplace from '@rollup/plugin-replace' // https://vitejs.dev/config/ @@ -13,5 +14,10 @@ export default defineConfig({ }, }), react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), ], }) diff --git a/examples/react/full-width-resizable-table/.gitignore b/examples/react/full-width-resizable-table/.gitignore deleted file mode 100644 index 53f7466aca..0000000000 --- a/examples/react/full-width-resizable-table/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules -.DS_Store -dist -dist-ssr -*.local \ No newline at end of file diff --git a/examples/react/full-width-resizable-table/README.md b/examples/react/full-width-resizable-table/README.md deleted file mode 100755 index a2fd61f947..0000000000 --- a/examples/react/full-width-resizable-table/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Full Width Resizable Table - -- [Open this example in a new CodeSandbox](https://codesandbox.io/s/github/tanstack/table/tree/main/examples/react/full-width-resizable-table) -- `yarn` and `yarn start` to run and edit the example diff --git a/examples/react/full-width-resizable-table/index.html b/examples/react/full-width-resizable-table/index.html deleted file mode 100644 index 3fc40c9367..0000000000 --- a/examples/react/full-width-resizable-table/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - Vite App - - - -
- - - diff --git a/examples/react/full-width-resizable-table/package.json b/examples/react/full-width-resizable-table/package.json deleted file mode 100755 index 9e03368b3a..0000000000 --- a/examples/react/full-width-resizable-table/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "tanstack-table-example-full-width-resizable-table", - "version": "0.0.0", - "private": true, - "scripts": { - "dev": "vite", - "build": "vite build", - "serve": "vite preview", - "start": "vite" - }, - "dependencies": { - "@faker-js/faker": "^8.4.1", - "@tanstack/react-table": "^8.20.6", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "typescript": "5.4.5", - "vite": "^5.3.2" - } -} diff --git a/examples/react/full-width-resizable-table/src/index.css b/examples/react/full-width-resizable-table/src/index.css deleted file mode 100755 index cad3351a76..0000000000 --- a/examples/react/full-width-resizable-table/src/index.css +++ /dev/null @@ -1,53 +0,0 @@ -html { - font-family: sans-serif; - font-size: 14px; -} - -table { - border: 1px solid lightgray; -} - -tbody { - border-bottom: 1px solid lightgray; -} - -th { - border-bottom: 1px solid lightgray; - border-right: 1px solid lightgray; - padding: 2px 4px; -} - -tfoot { - color: gray; -} - -tfoot th { - font-weight: normal; -} - -.resizer { - position: absolute; - right: 0; - top: 0; - height: 100%; - width: 5px; - background: rgba(0, 0, 0, 0.5); - cursor: col-resize; - user-select: none; - touch-action: none; -} - -.resizer.isResizing { - background: blue; - opacity: 1; -} - -@media (hover: hover) { - .resizer { - opacity: 0; - } - - *:hover > .resizer { - opacity: 1; - } -} diff --git a/examples/react/full-width-resizable-table/src/main.tsx b/examples/react/full-width-resizable-table/src/main.tsx deleted file mode 100644 index 7ac6e8dd6f..0000000000 --- a/examples/react/full-width-resizable-table/src/main.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import './index.css' - -import { - getCoreRowModel, - ColumnDef, - flexRender, - useReactTable, -} from '@tanstack/react-table' -import { makeData, Person } from './makeData' - -const columns: ColumnDef[] = [ - { - header: 'Name', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'firstName', - cell: info => info.getValue(), - footer: props => props.column.id, - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - cell: info => info.getValue(), - header: () => Last Name, - footer: props => props.column.id, - }, - ], - }, - { - header: 'Info', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'age', - header: () => 'Age', - footer: props => props.column.id, - }, - { - header: 'More Info', - columns: [ - { - accessorKey: 'visits', - header: () => Visits, - footer: props => props.column.id, - }, - { - accessorKey: 'status', - header: 'Status', - footer: props => props.column.id, - }, - { - accessorKey: 'progress', - header: 'Profile Progress', - footer: props => props.column.id, - }, - ], - }, - ], - }, -] - -function App() { - const data = React.useMemo(() => makeData(20), []) - - const table = useReactTable({ - data, - columns, - enableColumnResizing: true, - columnResizeMode: 'onChange', - getCoreRowModel: getCoreRowModel(), - debugTable: true, - debugHeaders: true, - debugColumns: true, - }) - - return ( -
-
- - - {table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(header => { - return ( - - ) - })} - - ))} - - - {table.getRowModel().rows.map(row => { - return ( - - {row.getVisibleCells().map(cell => { - return ( - - ) - })} - - ) - })} - -
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} - {header.column.getCanResize() && ( -
- )} -
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} -
-
-
- ) -} - -const rootElement = document.getElementById('root') -if (!rootElement) throw new Error('Failed to find the root element') - -ReactDOM.createRoot(rootElement).render( - - - -) diff --git a/examples/react/full-width-resizable-table/src/makeData.ts b/examples/react/full-width-resizable-table/src/makeData.ts deleted file mode 100755 index 71c62e784b..0000000000 --- a/examples/react/full-width-resizable-table/src/makeData.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { faker } from '@faker-js/faker' - -export type Person = { - firstName: string - lastName: string - age: number - visits: number - progress: number - status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] -} - -const range = (len: number) => { - const arr: Array = [] - for (let i = 0; i < len; i++) { - arr.push(i) - } - return arr -} - -const newPerson = (): Person => { - return { - firstName: faker.person.firstName(), - lastName: faker.person.lastName(), - age: faker.number.int(40), - visits: faker.number.int(1000), - progress: faker.number.int(100), - status: faker.helpers.shuffle([ - 'relationship', - 'complicated', - 'single', - ])[0]!, - } -} - -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! - return range(len).map((d): Person => { - return { - ...newPerson(), - subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, - } - }) - } - - return makeDataLevel() -} diff --git a/examples/react/full-width-resizable-table/tsconfig.json b/examples/react/full-width-resizable-table/tsconfig.json deleted file mode 100644 index 6d545f543f..0000000000 --- a/examples/react/full-width-resizable-table/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"] -} diff --git a/examples/react/full-width-resizable-table/vite.config.js b/examples/react/full-width-resizable-table/vite.config.js deleted file mode 100644 index 2e1361723a..0000000000 --- a/examples/react/full-width-resizable-table/vite.config.js +++ /dev/null @@ -1,17 +0,0 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' -import rollupReplace from '@rollup/plugin-replace' - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [ - rollupReplace({ - preventAssignment: true, - values: { - __DEV__: JSON.stringify(true), - 'process.env.NODE_ENV': JSON.stringify('development'), - }, - }), - react(), - ], -}) diff --git a/examples/react/full-width-table/.gitignore b/examples/react/full-width-table/.gitignore deleted file mode 100755 index 4d29575de8..0000000000 --- a/examples/react/full-width-table/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# production -/build - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* diff --git a/examples/react/full-width-table/README.md b/examples/react/full-width-table/README.md deleted file mode 100755 index 866e19028a..0000000000 --- a/examples/react/full-width-table/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Full Width Table (with collapsible cells) - -- [Open this example in a new CodeSandbox](https://codesandbox.io/s/github/tanstack/react-table/tree/master/examples/full-width-table) -- `yarn` and `yarn start` to run and edit the example diff --git a/examples/react/full-width-table/index.html b/examples/react/full-width-table/index.html deleted file mode 100644 index 3fc40c9367..0000000000 --- a/examples/react/full-width-table/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - Vite App - - - -
- - - diff --git a/examples/react/full-width-table/package.json b/examples/react/full-width-table/package.json deleted file mode 100755 index 0617f52012..0000000000 --- a/examples/react/full-width-table/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "tanstack-table-example-full-width", - "version": "0.0.0", - "private": true, - "scripts": { - "dev": "vite", - "build": "vite build", - "serve": "vite preview", - "start": "vite" - }, - "dependencies": { - "@faker-js/faker": "^8.4.1", - "@tanstack/react-table": "^8.20.6", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "typescript": "5.4.5", - "vite": "^5.3.2" - } -} diff --git a/examples/react/full-width-table/src/index.css b/examples/react/full-width-table/src/index.css deleted file mode 100755 index 43c09e0f6b..0000000000 --- a/examples/react/full-width-table/src/index.css +++ /dev/null @@ -1,26 +0,0 @@ -html { - font-family: sans-serif; - font-size: 14px; -} - -table { - border: 1px solid lightgray; -} - -tbody { - border-bottom: 1px solid lightgray; -} - -th { - border-bottom: 1px solid lightgray; - border-right: 1px solid lightgray; - padding: 2px 4px; -} - -tfoot { - color: gray; -} - -tfoot th { - font-weight: normal; -} diff --git a/examples/react/full-width-table/src/main.tsx b/examples/react/full-width-table/src/main.tsx deleted file mode 100644 index 3312784123..0000000000 --- a/examples/react/full-width-table/src/main.tsx +++ /dev/null @@ -1,219 +0,0 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import './index.css' - -import { - PaginationState, - useReactTable, - getCoreRowModel, - getPaginationRowModel, - ColumnDef, - flexRender, -} from '@tanstack/react-table' -import { makeData, Person } from './makeData' - -function App() { - const rerender = React.useReducer(() => ({}), {})[1] - - const columns = React.useMemo[]>( - () => [ - { - header: 'Name', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'firstName', - cell: info => info.getValue(), - footer: props => props.column.id, - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - cell: info => info.getValue(), - header: () => Last Name, - footer: props => props.column.id, - }, - ], - }, - { - header: 'Info', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'age', - header: () => 'Age', - footer: props => props.column.id, - }, - { - accessorKey: 'visits', - header: () => Visits, - footer: props => props.column.id, - }, - { - accessorKey: 'status', - header: 'Status', - footer: props => props.column.id, - }, - { - accessorKey: 'progress', - header: 'Profile Progress', - footer: props => props.column.id, - }, - ], - }, - ], - [] - ) - - const [data, setData] = React.useState(() => makeData(100000)) - const refreshData = () => setData(() => makeData(100000)) - - const [pagination, setPagination] = React.useState({ - pageIndex: 0, - pageSize: 10, - }) - - const table = useReactTable({ - data, - columns, - state: { - pagination, - }, - onPaginationChange: setPagination, - // Pipeline - getCoreRowModel: getCoreRowModel(), - getPaginationRowModel: getPaginationRowModel(), - debugTable: true, - }) - - return ( - <> -
-
- - - {table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(header => { - return ( - - ) - })} - - ))} - - - {table.getRowModel().rows.map(row => { - return ( - - {row.getVisibleCells().map(cell => { - return ( - - ) - })} - - ) - })} - -
- {header.isPlaceholder ? null : ( -
- {flexRender( - header.column.columnDef.header, - header.getContext() - )} -
- )} -
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} -
-
-
- - - - - -
Page
- - {table.getState().pagination.pageIndex + 1} of{' '} - {table.getPageCount()} - -
- - | Go to page: - { - const page = e.target.value ? Number(e.target.value) - 1 : 0 - table.setPageIndex(page) - }} - className="border p-1 rounded w-16" - /> - - -
-
{table.getRowModel().rows.length} Rows
-
- -
-
- -
-
- -
-
{JSON.stringify(pagination, null, 2)}
- - ) -} - -const rootElement = document.getElementById('root') -if (!rootElement) throw new Error('Failed to find the root element') - -ReactDOM.createRoot(rootElement).render( - - - -) diff --git a/examples/react/full-width-table/src/makeData.ts b/examples/react/full-width-table/src/makeData.ts deleted file mode 100755 index 71c62e784b..0000000000 --- a/examples/react/full-width-table/src/makeData.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { faker } from '@faker-js/faker' - -export type Person = { - firstName: string - lastName: string - age: number - visits: number - progress: number - status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] -} - -const range = (len: number) => { - const arr: Array = [] - for (let i = 0; i < len; i++) { - arr.push(i) - } - return arr -} - -const newPerson = (): Person => { - return { - firstName: faker.person.firstName(), - lastName: faker.person.lastName(), - age: faker.number.int(40), - visits: faker.number.int(1000), - progress: faker.number.int(100), - status: faker.helpers.shuffle([ - 'relationship', - 'complicated', - 'single', - ])[0]!, - } -} - -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! - return range(len).map((d): Person => { - return { - ...newPerson(), - subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, - } - }) - } - - return makeDataLevel() -} diff --git a/examples/react/full-width-table/tsconfig.json b/examples/react/full-width-table/tsconfig.json deleted file mode 100644 index 6d545f543f..0000000000 --- a/examples/react/full-width-table/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"] -} diff --git a/examples/react/full-width-table/vite.config.js b/examples/react/full-width-table/vite.config.js deleted file mode 100644 index 2e1361723a..0000000000 --- a/examples/react/full-width-table/vite.config.js +++ /dev/null @@ -1,17 +0,0 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' -import rollupReplace from '@rollup/plugin-replace' - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [ - rollupReplace({ - preventAssignment: true, - values: { - __DEV__: JSON.stringify(true), - 'process.env.NODE_ENV': JSON.stringify('development'), - }, - }), - react(), - ], -}) diff --git a/examples/react/fully-controlled/index.html b/examples/react/fully-controlled/index.html deleted file mode 100644 index 3fc40c9367..0000000000 --- a/examples/react/fully-controlled/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - Vite App - - - -
- - - diff --git a/examples/react/fully-controlled/package.json b/examples/react/fully-controlled/package.json deleted file mode 100644 index af37975c68..0000000000 --- a/examples/react/fully-controlled/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "tanstack-table-example-fully-controlled", - "version": "0.0.0", - "private": true, - "scripts": { - "dev": "vite", - "build": "vite build", - "serve": "vite preview", - "start": "vite" - }, - "dependencies": { - "@faker-js/faker": "^8.4.1", - "@tanstack/react-table": "^8.20.6", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "typescript": "5.4.5", - "vite": "^5.3.2" - } -} diff --git a/examples/react/fully-controlled/src/index.css b/examples/react/fully-controlled/src/index.css deleted file mode 100644 index 43c09e0f6b..0000000000 --- a/examples/react/fully-controlled/src/index.css +++ /dev/null @@ -1,26 +0,0 @@ -html { - font-family: sans-serif; - font-size: 14px; -} - -table { - border: 1px solid lightgray; -} - -tbody { - border-bottom: 1px solid lightgray; -} - -th { - border-bottom: 1px solid lightgray; - border-right: 1px solid lightgray; - padding: 2px 4px; -} - -tfoot { - color: gray; -} - -tfoot th { - font-weight: normal; -} diff --git a/examples/react/fully-controlled/src/main.tsx b/examples/react/fully-controlled/src/main.tsx deleted file mode 100644 index caa67761c4..0000000000 --- a/examples/react/fully-controlled/src/main.tsx +++ /dev/null @@ -1,235 +0,0 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' - -import './index.css' - -import { - ColumnDef, - flexRender, - getCoreRowModel, - getPaginationRowModel, - useReactTable, -} from '@tanstack/react-table' -import { makeData } from './makeData' - -type Person = { - firstName: string - lastName: string - age: number - visits: number - status: string - progress: number -} - -const defaultColumns: ColumnDef[] = [ - { - header: 'Name', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'firstName', - cell: info => info.getValue(), - footer: props => props.column.id, - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - cell: info => info.getValue(), - header: () => Last Name, - footer: props => props.column.id, - }, - ], - }, - { - header: 'Info', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'age', - header: () => 'Age', - footer: props => props.column.id, - }, - { - header: 'More Info', - columns: [ - { - accessorKey: 'visits', - header: () => Visits, - footer: props => props.column.id, - }, - { - accessorKey: 'status', - header: 'Status', - footer: props => props.column.id, - }, - { - accessorKey: 'progress', - header: 'Profile Progress', - footer: props => props.column.id, - }, - ], - }, - ], - }, -] - -function App() { - const [data] = React.useState(() => makeData(1000)) - const [columns] = React.useState(() => [ - ...defaultColumns, - ]) - - const rerender = React.useReducer(() => ({}), {})[1] - - // Create the table and pass your options - const table = useReactTable({ - data, - columns, - getCoreRowModel: getCoreRowModel(), - getPaginationRowModel: getPaginationRowModel(), - }) - - // Manage your own state - const [state, setState] = React.useState(table.initialState) - - // Override the state managers for the table to your own - table.setOptions(prev => ({ - ...prev, - state, - onStateChange: setState, - // These are just table options, so if things - // need to change based on your state, you can - // derive them here - - // Just for fun, let's debug everything if the pageIndex - // is greater than 2 - debugTable: state.pagination.pageIndex > 2, - })) - - return ( -
- - - {table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(header => ( - - ))} - - ))} - - - {table.getRowModel().rows.map(row => ( - - {row.getVisibleCells().map(cell => ( - - ))} - - ))} - - - {table.getFooterGroups().map(footerGroup => ( - - {footerGroup.headers.map(header => ( - - ))} - - ))} - -
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} -
- {flexRender(cell.column.columnDef.cell, cell.getContext())} -
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.footer, - header.getContext() - )} -
-
-
- - - - - -
Page
- - {table.getState().pagination.pageIndex + 1} of{' '} - {table.getPageCount()} - -
- - | Go to page: - { - const page = e.target.value ? Number(e.target.value) - 1 : 0 - table.setPageIndex(page) - }} - className="border p-1 rounded w-16" - /> - - -
-
- -
- ) -} - -const rootElement = document.getElementById('root') -if (!rootElement) throw new Error('Failed to find the root element') - -ReactDOM.createRoot(rootElement).render( - - - -) diff --git a/examples/react/fully-controlled/src/makeData.ts b/examples/react/fully-controlled/src/makeData.ts deleted file mode 100644 index 331dd1eb19..0000000000 --- a/examples/react/fully-controlled/src/makeData.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { faker } from '@faker-js/faker' - -export type Person = { - firstName: string - lastName: string - age: number - visits: number - progress: number - status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] -} - -const range = (len: number) => { - const arr: number[] = [] - for (let i = 0; i < len; i++) { - arr.push(i) - } - return arr -} - -const newPerson = (): Person => { - return { - firstName: faker.person.firstName(), - lastName: faker.person.lastName(), - age: faker.number.int(40), - visits: faker.number.int(1000), - progress: faker.number.int(100), - status: faker.helpers.shuffle([ - 'relationship', - 'complicated', - 'single', - ])[0]!, - } -} - -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! - return range(len).map((d): Person => { - return { - ...newPerson(), - subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, - } - }) - } - - return makeDataLevel() -} diff --git a/examples/react/fully-controlled/tsconfig.json b/examples/react/fully-controlled/tsconfig.json deleted file mode 100644 index 6d545f543f..0000000000 --- a/examples/react/fully-controlled/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"] -} diff --git a/examples/react/fully-controlled/vite.config.js b/examples/react/fully-controlled/vite.config.js deleted file mode 100644 index 2e1361723a..0000000000 --- a/examples/react/fully-controlled/vite.config.js +++ /dev/null @@ -1,17 +0,0 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' -import rollupReplace from '@rollup/plugin-replace' - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [ - rollupReplace({ - preventAssignment: true, - values: { - __DEV__: JSON.stringify(true), - 'process.env.NODE_ENV': JSON.stringify('development'), - }, - }), - react(), - ], -}) diff --git a/examples/react/grouping/index.html b/examples/react/grouping/index.html index 3fc40c9367..1551290f72 100644 --- a/examples/react/grouping/index.html +++ b/examples/react/grouping/index.html @@ -4,7 +4,7 @@ Vite App - +
diff --git a/examples/react/grouping/package.json b/examples/react/grouping/package.json index 6fd672d98d..da50e2fb94 100644 --- a/examples/react/grouping/package.json +++ b/examples/react/grouping/package.json @@ -1,25 +1,28 @@ { - "name": "tanstack-table-example-grouping", - "version": "0.0.0", + "name": "tanstack-react-table-example-grouping", "private": true, "scripts": { "dev": "vite", "build": "vite build", "serve": "vite preview", - "start": "vite" + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" }, "dependencies": { - "@faker-js/faker": "^8.4.1", - "@tanstack/react-table": "^8.20.6", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "@faker-js/faker": "^10.4.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6" }, "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/react/grouping/src/index.css b/examples/react/grouping/src/index.css index 43c09e0f6b..b9af6ded32 100644 --- a/examples/react/grouping/src/index.css +++ b/examples/react/grouping/src/index.css @@ -5,6 +5,8 @@ html { table { border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; } tbody { @@ -24,3 +26,340 @@ tfoot { tfoot th { font-weight: normal; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/grouping/src/main.tsx b/examples/react/grouping/src/main.tsx index 24603780b9..a2d8a87275 100644 --- a/examples/react/grouping/src/main.tsx +++ b/examples/react/grouping/src/main.tsx @@ -1,115 +1,116 @@ import React from 'react' import ReactDOM from 'react-dom/client' - import './index.css' - import { - GroupingState, - useReactTable, - getPaginationRowModel, - getFilteredRowModel, - getCoreRowModel, - getGroupedRowModel, - getExpandedRowModel, - ColumnDef, - flexRender, + aggregationFns, + columnFilteringFeature, + columnGroupingFeature, + createExpandedRowModel, + createFilteredRowModel, + createGroupedRowModel, + createPaginatedRowModel, + createSortedRowModel, + createTableHook, + filterFns, + rowExpandingFeature, + rowPaginationFeature, + rowSortingFeature, + sortFns, } from '@tanstack/react-table' -import { makeData, Person } from './makeData' +import { makeData } from './makeData' +import type { Person } from './makeData' + +// this example happens to use the createTableHook pattern, but it is not required +const { useAppTable, createAppColumnHelper } = createTableHook({ + _features: { + columnFilteringFeature, + columnGroupingFeature, + rowExpandingFeature, + rowPaginationFeature, + rowSortingFeature, + }, + _rowModels: { + expandedRowModel: createExpandedRowModel(), + filteredRowModel: createFilteredRowModel(filterFns), + groupedRowModel: createGroupedRowModel(aggregationFns), + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, +}) + +const columnHelper = createAppColumnHelper() function App() { const rerender = React.useReducer(() => ({}), {})[1] - const columns = React.useMemo[]>( - () => [ - { - header: 'Name', - columns: [ - { - accessorKey: 'firstName', - header: 'First Name', - cell: info => info.getValue(), - /** - * override the value used for row grouping - * (otherwise, defaults to the value derived from accessorKey / accessorFn) - */ - getGroupingValue: row => `${row.firstName} ${row.lastName}`, - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - header: () => Last Name, - cell: info => info.getValue(), - }, - ], - }, - { - header: 'Info', - columns: [ - { - accessorKey: 'age', - header: () => 'Age', - aggregatedCell: ({ getValue }) => - Math.round(getValue() * 100) / 100, - aggregationFn: 'median', - }, - { - header: 'More Info', - columns: [ - { - accessorKey: 'visits', - header: () => Visits, - aggregationFn: 'sum', - // aggregatedCell: ({ getValue }) => getValue().toLocaleString(), - }, - { - accessorKey: 'status', - header: 'Status', - }, - { - accessorKey: 'progress', - header: 'Profile Progress', - cell: ({ getValue }) => - Math.round(getValue() * 100) / 100 + '%', - aggregationFn: 'mean', - aggregatedCell: ({ getValue }) => - Math.round(getValue() * 100) / 100 + '%', - }, - ], - }, - ], - }, - ], - [] + const columns = React.useMemo( + () => + columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + cell: (info) => info.getValue(), + /** + * override the value used for row grouping + * (otherwise, defaults to the value derived from accessorKey / accessorFn) + */ + getGroupingValue: (row) => `${row.firstName} ${row.lastName}`, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + header: () => Last Name, + cell: (info) => info.getValue(), + }), + columnHelper.accessor('age', { + header: () => 'Age', + aggregatedCell: ({ getValue }) => + Math.round(getValue() * 100) / 100, + aggregationFn: 'median', + }), + columnHelper.accessor('visits', { + header: () => Visits, + aggregationFn: 'sum', + aggregatedCell: ({ getValue }) => getValue().toLocaleString(), + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + cell: ({ getValue }) => + Math.round(getValue() * 100) / 100 + '%', + aggregationFn: 'mean', + aggregatedCell: ({ getValue }) => + Math.round(getValue() * 100) / 100 + '%', + }), + ]), + [], ) - const [data, setData] = React.useState(() => makeData(100000)) - const refreshData = () => setData(() => makeData(100000)) - - const [grouping, setGrouping] = React.useState([]) + const [data, setData] = React.useState(() => makeData(10_000)) + const refreshData = () => setData(makeData(10_000)) + const stressTest = () => setData(makeData(200_000)) - const table = useReactTable({ - data, - columns, - state: { - grouping, + const table = useAppTable( + { + columns, + data, + debugTable: true, }, - onGroupingChange: setGrouping, - getExpandedRowModel: getExpandedRowModel(), - getGroupedRowModel: getGroupedRowModel(), - getCoreRowModel: getCoreRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getFilteredRowModel: getFilteredRowModel(), - debugTable: true, - }) + (state) => state, // default selector + ) return ( -
-
+
+
+ + +
+
- {table.getHeaderGroups().map(headerGroup => ( + {table.getHeaderGroups().map((headerGroup) => ( - {headerGroup.headers.map(header => { + {headerGroup.headers.map((header) => { return ( @@ -142,60 +136,44 @@ function App() { ))} - {table.getRowModel().rows.map(row => { + {table.getRowModel().rows.map((row) => { return ( - {row.getVisibleCells().map(cell => { + {row.getAllCells().map((cell) => { return ( ) @@ -205,78 +183,75 @@ function App() { })}
{header.isPlaceholder ? null : ( @@ -117,22 +118,15 @@ function App() { {header.column.getCanGroup() ? ( // If the header can be grouped, let's add a toggle ) : null}{' '} - {flexRender( - header.column.columnDef.header, - header.getContext() - )} + )}
{cell.getIsGrouped() ? ( // If it's a grouped cell, add an expander and row count <> ) : cell.getIsAggregated() ? ( // If the cell is aggregated, use the Aggregated // renderer for cell - flexRender( - cell.column.columnDef.aggregatedCell ?? - cell.column.columnDef.cell, - cell.getContext() - ) + ) : cell.getIsPlaceholder() ? null : ( // For cells with repeated values, render null // Otherwise, just render the regular cell - flexRender( - cell.column.columnDef.cell, - cell.getContext() - ) + )}
-
-
+
+
- +
Page
- {table.getState().pagination.pageIndex + 1} of{' '} - {table.getPageCount()} + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()}
- + | Go to page: { + defaultValue={table.state.pagination.pageIndex + 1} + onChange={(e) => { const page = e.target.value ? Number(e.target.value) - 1 : 0 table.setPageIndex(page) }} - className="border p-1 rounded w-16" + className="page-size-input" />
-
{table.getRowModel().rows.length} Rows
+
{table.getRowModel().rows.length.toLocaleString()} Rows
-
- -
-
{JSON.stringify(grouping, null, 2)}
+
{JSON.stringify(table.state, null, 2)}
) } @@ -287,5 +262,5 @@ if (!rootElement) throw new Error('Failed to find the root element') ReactDOM.createRoot(rootElement).render( - + , ) diff --git a/examples/react/grouping/src/makeData.ts b/examples/react/grouping/src/makeData.ts index 331dd1eb19..95302cdf16 100644 --- a/examples/react/grouping/src/makeData.ts +++ b/examples/react/grouping/src/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,14 +29,14 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! - return range(len).map((d): Person => { +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_): Person => { return { ...newPerson(), subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, diff --git a/examples/react/grouping/src/vite-env.d.ts b/examples/react/grouping/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/grouping/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/grouping/tsconfig.json b/examples/react/grouping/tsconfig.json index 6d545f543f..33a3e872be 100644 --- a/examples/react/grouping/tsconfig.json +++ b/examples/react/grouping/tsconfig.json @@ -5,20 +5,17 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, - "include": ["src"] + "include": ["src", "vite.config.js", "vite.config.ts"] } diff --git a/examples/react/grouping/vite.config.js b/examples/react/grouping/vite.config.js index 2e1361723a..1755f6bea8 100644 --- a/examples/react/grouping/vite.config.js +++ b/examples/react/grouping/vite.config.js @@ -1,5 +1,6 @@ import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' import rollupReplace from '@rollup/plugin-replace' // https://vitejs.dev/config/ @@ -13,5 +14,10 @@ export default defineConfig({ }, }), react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), ], }) diff --git a/examples/react/kitchen-sink-hero-ui/.gitignore b/examples/react/kitchen-sink-hero-ui/.gitignore new file mode 100644 index 0000000000..18a5070e2a --- /dev/null +++ b/examples/react/kitchen-sink-hero-ui/.gitignore @@ -0,0 +1,6 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local +!lib diff --git a/examples/react/kitchen-sink-hero-ui/README.md b/examples/react/kitchen-sink-hero-ui/README.md new file mode 100644 index 0000000000..c88adea2d1 --- /dev/null +++ b/examples/react/kitchen-sink-hero-ui/README.md @@ -0,0 +1,6 @@ +# React Kitchen Sink Hero UI Example + +To run this example: + +- `pnpm install` +- `pnpm start` diff --git a/examples/react/kitchen-sink-hero-ui/index.html b/examples/react/kitchen-sink-hero-ui/index.html new file mode 100644 index 0000000000..1551290f72 --- /dev/null +++ b/examples/react/kitchen-sink-hero-ui/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/react/kitchen-sink-hero-ui/package.json b/examples/react/kitchen-sink-hero-ui/package.json new file mode 100644 index 0000000000..7b69207e7f --- /dev/null +++ b/examples/react/kitchen-sink-hero-ui/package.json @@ -0,0 +1,40 @@ +{ + "name": "tanstack-react-table-example-kitchen-sink-hero-ui", + "type": "module", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", + "@faker-js/faker": "^10.4.0", + "@heroui/react": "^3.0.4", + "@heroui/styles": "^3.0.4", + "@tailwindcss/vite": "^4.3.0", + "@tanstack/match-sorter-utils": "^9.0.0-alpha.4", + "@tanstack/react-pacer": "^0.22.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "date-fns": "^4.1.0", + "react": "^19.2.6", + "react-dom": "^19.2.6", + "tailwindcss": "^4.3.0" + }, + "devDependencies": { + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/react/kitchen-sink-hero-ui/src/lib/data-table.ts b/examples/react/kitchen-sink-hero-ui/src/lib/data-table.ts new file mode 100644 index 0000000000..24568c1a9d --- /dev/null +++ b/examples/react/kitchen-sink-hero-ui/src/lib/data-table.ts @@ -0,0 +1,502 @@ +import { + filterFn_arrIncludes, + filterFn_equals, + filterFn_equalsString, + filterFn_greaterThan, + filterFn_greaterThanOrEqualTo, + filterFn_includesString, + filterFn_lessThan, + filterFn_lessThanOrEqualTo, +} from '@tanstack/react-table' +import { rankItem } from '@tanstack/match-sorter-utils' +import type { + ExtendedColumnFilter, + FilterOperator, + JoinOperator, + TableFilterFeatures, +} from '@/types' +import type { RankingInfo } from '@tanstack/match-sorter-utils' +import type { + FilterFn, + Row, + RowData, + TableFeatures, +} from '@tanstack/react-table' + +/** + * Fuzzy filter using @tanstack/match-sorter-utils. Used as the global filter + * (`globalFilterFn: 'fuzzy'`) in the kitchen-sink example so the toolbar + * search ranks rows by best match across all columns. + * + * Mirrors the canonical pattern from `examples/react/filters-fuzzy`. Written + * as a plain function (not a typed const) so it stays generic over TFeatures + * and TData and can be slotted into any `createFilteredRowModel({...})` + * registration without narrowing inference. + */ +export function fuzzyFilter< + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + value: string, + addMeta?: (meta: { itemRank: RankingInfo }) => void, +): boolean { + // Rank the item + const itemRank = rankItem(row.getValue(columnId), value) + + // Store the itemRank info so a fuzzy sort function (if any) could reuse it + addMeta?.({ itemRank }) + + // Return whether the item should be filtered in/out + return itemRank.passed +} + +declare module '@tanstack/react-table' { + interface FilterFns { + fuzzy: FilterFn + } + interface FilterMeta { + itemRank?: RankingInfo + } +} + +function isFalsy(val: unknown) { + return ( + val === undefined || + val === null || + val === '' || + (Array.isArray(val) && val.length === 0) + ) +} + +function isValidDate(value: unknown): boolean { + if (value instanceof Date) return !isNaN(value.getTime()) + if (typeof value === 'string') return !isNaN(Date.parse(value)) + return false +} + +function toDate(value: unknown): Date | null { + if (value instanceof Date) return value + if (typeof value === 'string') { + const date = new Date(value) + return !isNaN(date.getTime()) ? date : null + } + return null +} + +function isSameDay(date1: Date, date2: Date): boolean { + const date1Str = date1.toISOString().split('T')[0] + const date2Str = date2.toISOString().split('T')[0] + return date1Str === date2Str +} + +const filterFn_enhancedEquals: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + filterValue: unknown, +) => { + const rowValue = row.getValue(columnId) + + if (Array.isArray(filterValue)) { + return filterFn_arrIncludes(row, columnId, filterValue) + } + + if (isValidDate(rowValue) && isValidDate(filterValue)) { + const rowDate = toDate(rowValue) + const filterDate = toDate(filterValue) + + if (rowDate && filterDate) { + return isSameDay(rowDate, filterDate) + } + } + + return filterFn_equals(row, columnId, filterValue) +} + +filterFn_enhancedEquals.resolveFilterValue = (val: any) => isFalsy(val) + +const filterFn_enhancedGreaterThan: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + filterValue: unknown, +) => { + const rowValue = row.getValue(columnId) + + if (isValidDate(rowValue) && isValidDate(filterValue)) { + const rowDate = toDate(rowValue) + const filterDate = toDate(filterValue) + + if (rowDate && filterDate) { + return rowDate.getTime() > filterDate.getTime() + } + } + + return filterFn_greaterThan(row, columnId, filterValue) +} + +filterFn_enhancedGreaterThan.resolveFilterValue = (val: any) => isFalsy(val) + +const filterFn_enhancedGreaterThanOrEqualTo: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + filterValue: unknown, +) => { + const rowValue = row.getValue(columnId) + + if (isValidDate(rowValue) && isValidDate(filterValue)) { + const rowDate = toDate(rowValue) + const filterDate = toDate(filterValue) + + if (rowDate && filterDate) { + return rowDate.getTime() >= filterDate.getTime() + } + } + + return filterFn_greaterThanOrEqualTo(row, columnId, filterValue) +} + +filterFn_enhancedGreaterThanOrEqualTo.resolveFilterValue = (val: any) => + isFalsy(val) + +const filterFn_enhancedLessThan: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + filterValue: unknown, +) => { + const rowValue = row.getValue(columnId) + + if (isValidDate(rowValue) && isValidDate(filterValue)) { + const rowDate = toDate(rowValue) + const filterDate = toDate(filterValue) + + if (rowDate && filterDate) { + return rowDate.getTime() < filterDate.getTime() + } + } + + return filterFn_lessThan(row, columnId, filterValue) +} + +filterFn_enhancedLessThan.resolveFilterValue = (val: any) => isFalsy(val) + +const filterFn_enhancedLessThanOrEqualTo: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + filterValue: unknown, +) => { + const rowValue = row.getValue(columnId) + + if (isValidDate(rowValue) && isValidDate(filterValue)) { + const rowDate = toDate(rowValue) + const filterDate = toDate(filterValue) + + if (rowDate && filterDate) { + return rowDate.getTime() <= filterDate.getTime() + } + } + + return filterFn_lessThanOrEqualTo(row, columnId, filterValue) +} + +filterFn_enhancedLessThanOrEqualTo.resolveFilterValue = (val: any) => + isFalsy(val) + +const filterFn_startsWith: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + filterValue: string, +) => { + const value = String(row.getValue(columnId) ?? '').toLowerCase() + return value.startsWith(filterValue.toLowerCase()) +} + +filterFn_startsWith.resolveFilterValue = (val: any) => isFalsy(val) + +const filterFn_endsWith: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + filterValue: string, +) => { + const value = String(row.getValue(columnId) ?? '').toLowerCase() + return value.endsWith(filterValue.toLowerCase()) +} + +filterFn_endsWith.resolveFilterValue = (val: any) => isFalsy(val) + +const filterFn_isEmpty: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, +) => { + const value = row.getValue(columnId) + return ( + value === undefined || + value === null || + value === '' || + (Array.isArray(value) && value.length === 0) + ) +} + +filterFn_isEmpty.resolveFilterValue = (val: any) => isFalsy(val) + +const filterFn_inBetween: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + filterValue: unknown, +) => { + if (Array.isArray(filterValue)) { + const [min, max] = filterValue + const rowValue: unknown = row.getValue(columnId) + + if (min === undefined || min === '' || min === null) { + return max === undefined || max === '' || max === null + ? true + : typeof rowValue === 'number' && typeof max === 'number' + ? rowValue <= max + : String(rowValue) <= String(max) + } + if (max === undefined || max === '' || max === null) { + return typeof rowValue === 'number' && typeof min === 'number' + ? rowValue >= min + : String(rowValue) >= String(min) + } + + if ( + rowValue instanceof Date || + (typeof rowValue === 'string' && !isNaN(Date.parse(rowValue))) + ) { + const dateValue = new Date(rowValue).getTime() + const minDate = new Date(min as string | Date).getTime() + const maxDate = new Date(max as string | Date).getTime() + return dateValue >= minDate && dateValue <= maxDate + } + + const numValue = Number(rowValue) + return ( + !isNaN(numValue) && numValue >= Number(min) && numValue <= Number(max) + ) + } + return true +} + +filterFn_inBetween.autoRemove = (val: any) => isFalsy(val) + +const filterFn_isRelativeToToday: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + filterValue: unknown, +) => { + const rowValue = row.getValue(columnId) + + if (!isValidDate(rowValue)) return false + + const rowDate = toDate(rowValue) + if (!rowDate) return false + + rowDate.setHours(0, 0, 0, 0) + + const today = new Date() + today.setHours(0, 0, 0, 0) + + const diffInDays = Math.floor( + (rowDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24), + ) + + if (typeof filterValue === 'number') { + return diffInDays === filterValue + } else if (typeof filterValue === 'string') { + const numValue = parseInt(filterValue, 10) + if (!isNaN(numValue)) { + return diffInDays === numValue + } + } else if (Array.isArray(filterValue) && filterValue.length === 2) { + const [min, max] = filterValue + const minDays = typeof min === 'number' ? min : parseInt(min as string, 10) + const maxDays = typeof max === 'number' ? max : parseInt(max as string, 10) + + if (!isNaN(minDays) && !isNaN(maxDays)) { + return diffInDays >= minDays && diffInDays <= maxDays + } + } + + return false +} + +filterFn_isRelativeToToday.autoRemove = (val: any) => isFalsy(val) + +export const dynamicFilterFn: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, TData>, + columnId: string, + filterValue: unknown, +) => { + let operator: FilterOperator = 'includesString' + let value = filterValue + let joinOperator: JoinOperator = 'and' + + const filters: Array = + row.table.store.state.columnFilters.filter((f) => f.id === columnId) + + if (!filters.length) return true + + if (filters[0].joinOperator) { + joinOperator = filters[0].joinOperator + } + + return filters.reduce((pass, filter, index) => { + operator = filter.operator ?? 'includesString' + value = filter.value + + if (isFalsy(value) && operator !== 'isEmpty' && operator !== 'isNotEmpty') { + return pass + } + + let result: boolean + switch (operator) { + case 'includesString': + result = filterFn_includesString(row, columnId, value) + break + case 'notIncludesString': + result = !filterFn_includesString(row, columnId, value) + break + case 'equalsString': + result = filterFn_equalsString(row, columnId, value) + break + case 'notEqualsString': + result = !filterFn_equalsString(row, columnId, value) + break + case 'startsWith': + result = filterFn_startsWith(row, columnId, value) + break + case 'endsWith': + result = filterFn_endsWith(row, columnId, value) + break + case 'isEmpty': + result = filterFn_isEmpty(row, columnId, '') + break + case 'isNotEmpty': + result = !filterFn_isEmpty(row, columnId, '') + break + case 'equals': + result = filterFn_enhancedEquals(row, columnId, value) + break + case 'notEquals': + result = !filterFn_enhancedEquals(row, columnId, value) + break + case 'greaterThan': + result = filterFn_enhancedGreaterThan(row, columnId, value) + break + case 'greaterThanOrEqualTo': + result = filterFn_enhancedGreaterThanOrEqualTo(row, columnId, value) + break + case 'lessThan': + result = filterFn_enhancedLessThan(row, columnId, value) + break + case 'lessThanOrEqualTo': + result = filterFn_enhancedLessThanOrEqualTo(row, columnId, value) + break + case 'inRange': + result = filterFn_inBetween(row, columnId, value) + break + case 'isRelativeToToday': + result = filterFn_isRelativeToToday(row, columnId, value) + break + default: + result = filterFn_includesString(row, columnId, value) + break + } + + if (index === 0) return result + + return joinOperator === 'and' ? pass && result : pass || result + }, true) +} + +export function getFilterOperators(type: string): Array<{ + label: string + value: FilterOperator +}> { + switch (type) { + case 'text': + return [ + { label: 'contains', value: 'includesString' }, + { label: 'does not contain', value: 'notIncludesString' }, + { label: 'starts with', value: 'startsWith' }, + { label: 'ends with', value: 'endsWith' }, + { label: 'is', value: 'equalsString' }, + { label: 'is not', value: 'notEqualsString' }, + { label: 'is empty', value: 'isEmpty' }, + { label: 'is not empty', value: 'isNotEmpty' }, + ] + case 'number': + return [ + { label: 'is', value: 'equals' }, + { label: 'is not', value: 'notEquals' }, + { label: 'is less than', value: 'lessThan' }, + { label: 'is less than or equal to', value: 'lessThanOrEqualTo' }, + { label: 'is greater than', value: 'greaterThan' }, + { label: 'is greater than or equal to', value: 'greaterThanOrEqualTo' }, + { label: 'is between', value: 'inRange' }, + ] + case 'date': + return [ + { label: 'is', value: 'equals' }, + { label: 'is not', value: 'notEquals' }, + { label: 'is before', value: 'lessThan' }, + { label: 'is on or before', value: 'lessThanOrEqualTo' }, + { label: 'is after', value: 'greaterThan' }, + { label: 'is on or after', value: 'greaterThanOrEqualTo' }, + { label: 'is between', value: 'inRange' }, + { label: 'is relative to today', value: 'isRelativeToToday' }, + { label: 'is empty', value: 'isEmpty' }, + { label: 'is not empty', value: 'isNotEmpty' }, + ] + case 'select': + case 'multi-select': + return [ + { label: 'is', value: 'equals' }, + { label: 'is not', value: 'notEquals' }, + { label: 'is empty', value: 'isEmpty' }, + { label: 'is not empty', value: 'isNotEmpty' }, + ] + default: + return [ + { label: 'contains', value: 'includesString' }, + { label: 'does not contain', value: 'notIncludesString' }, + { label: 'is', value: 'equalsString' }, + { label: 'is not', value: 'notEqualsString' }, + ] + } +} diff --git a/examples/react/kitchen-sink-hero-ui/src/lib/make-data.ts b/examples/react/kitchen-sink-hero-ui/src/lib/make-data.ts new file mode 100644 index 0000000000..8198ee2581 --- /dev/null +++ b/examples/react/kitchen-sink-hero-ui/src/lib/make-data.ts @@ -0,0 +1,57 @@ +import { faker } from '@faker-js/faker' + +export const statuses = ['active', 'inactive', 'pending'] as const +export const departments = [ + 'engineering', + 'marketing', + 'finance', + 'sales', + 'hr', +] as const + +export interface Person { + id: string + firstName: string + lastName: string + age: number + email: string + status: (typeof statuses)[number] + department: (typeof departments)[number] + joinDate: Date + subRows?: Array +} + +function range(len: number) { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +function newPerson(): Person { + return { + id: faker.string.uuid(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int({ min: 20, max: 65 }), + email: faker.internet.email(), + status: faker.helpers.arrayElement(statuses), + department: faker.helpers.arrayElement(departments), + joinDate: faker.date.past({ years: 5 }), + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/react/kitchen-sink-hero-ui/src/main.tsx b/examples/react/kitchen-sink-hero-ui/src/main.tsx new file mode 100644 index 0000000000..d3053ddd48 --- /dev/null +++ b/examples/react/kitchen-sink-hero-ui/src/main.tsx @@ -0,0 +1,1565 @@ +'use client' + +import * as React from 'react' +import * as ReactDOM from 'react-dom/client' +import { useDebouncedCallback } from '@tanstack/react-pacer/debouncer' +import { + DndContext, + PointerSensor, + closestCenter, + useSensor, + useSensors, +} from '@dnd-kit/core' +import { + SortableContext, + arrayMove, + useSortable, + verticalListSortingStrategy, +} from '@dnd-kit/sortable' +import { CSS } from '@dnd-kit/utilities' +import { + Button, + Checkbox, + Chip, + Dropdown, + Pagination as HeroPagination, + Table as HeroTable, + Input, + Label, + ListBox, + ListBoxItem, + Popover, + ProgressBar, + Select, + Surface, + Switch, + Tooltip, + cn, + useTheme, +} from '@heroui/react' +import { + aggregationFns, + columnFacetingFeature, + columnFilteringFeature, + columnGroupingFeature, + columnOrderingFeature, + columnPinningFeature, + columnResizingFeature, + columnSizingFeature, + columnVisibilityFeature, + createColumnHelper, + createCoreRowModel, + createExpandedRowModel, + createFacetedRowModel, + createFacetedUniqueValues, + createFilteredRowModel, + createGroupedRowModel, + createPaginatedRowModel, + createSortedRowModel, + filterFns, + globalFilteringFeature, + rowExpandingFeature, + rowPaginationFeature, + rowSelectionFeature, + rowSortingFeature, + sortFns, + tableFeatures, + useTable, +} from '@tanstack/react-table' +import type { DragEndEvent } from '@dnd-kit/core' +import type { Key } from '@heroui/react' +import type { Person } from '@/lib/make-data' +import type { + CellData, + Column, + ColumnPinningState, + ColumnSizingState, + ExpandedState, + GroupingState, + Header, + RowData, + SortingState, + Table, + TableFeatures, +} from '@tanstack/react-table' +import type { ExtendedColumnFilter } from '@/types' + +import { + dynamicFilterFn, + fuzzyFilter, + getFilterOperators, +} from '@/lib/data-table' +import { departments, makeData, statuses } from '@/lib/make-data' +import './styles/globals.css' + +declare module '@tanstack/react-table' { + interface ColumnMeta< + TFeatures extends TableFeatures, + TData extends RowData, + TValue extends CellData = CellData, + > { + label?: string + variant?: 'text' | 'number' | 'date' | 'boolean' | 'select' | 'multi-select' + options?: Array<{ label: string; value: string; count?: number }> + } +} + +const _features = tableFeatures({ + rowSortingFeature, + rowPaginationFeature, + rowSelectionFeature, + rowExpandingFeature, + columnFilteringFeature, + columnFacetingFeature, + columnOrderingFeature, + columnVisibilityFeature, + columnSizingFeature, + columnResizingFeature, + columnPinningFeature, + columnGroupingFeature, + globalFilteringFeature, +}) + +const columnHelper = createColumnHelper() +type AppTable = Table +type AppColumn = Column + +function getPageItems(pageIndex: number, pageCount: number) { + const currentPage = pageIndex + 1 + const pages = new Set([ + 1, + pageCount, + currentPage - 1, + currentPage, + currentPage + 1, + ]) + + return Array.from(pages) + .filter((page) => page >= 1 && page <= pageCount) + .sort((a, b) => a - b) + .reduce>((items, page) => { + const previous = items[items.length - 1] + if (typeof previous === 'number' && page - previous > 1) { + items.push('ellipsis') + } + items.push(page) + return items + }, []) +} + +function SortableFrame({ + id, + children, +}: { + id: string + children: React.ReactNode +}) { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id }) + + return ( +
+ {children} +
+ ) +} + +function toSentenceCase(value: string) { + return value + .replace(/[-_]/g, ' ') + .replace(/\w\S*/g, (word) => word[0].toUpperCase() + word.slice(1)) +} + +function formatDate(value: unknown) { + return new Intl.DateTimeFormat('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + }).format(new Date(String(value))) +} + +function toDateInputValue(value: unknown) { + if (!value) return '' + const date = new Date(String(value)) + return Number.isNaN(date.getTime()) ? '' : date.toISOString().slice(0, 10) +} + +function getAriaSort(sortDirection: false | 'asc' | 'desc') { + if (sortDirection === 'asc') return 'ascending' + if (sortDirection === 'desc') return 'descending' + return 'none' +} + +const SortingContext = React.createContext([]) + +function getSortDirection(sorting: SortingState, columnId: string) { + const sort = sorting.find((item) => item.id === columnId) + return sort ? (sort.desc ? 'desc' : 'asc') : undefined +} + +function getCommonPinningStyles( + column: AppColumn, + isSelected = false, +): React.CSSProperties { + const isPinned = column.getIsPinned() + const isLastLeftPinnedColumn = + isPinned === 'left' && column.getIsLastColumn('left') + const isFirstRightPinnedColumn = + isPinned === 'right' && column.getIsFirstColumn('right') + + return { + boxShadow: isLastLeftPinnedColumn + ? '-4px 0 4px -4px hsl(var(--heroui-border)) inset' + : isFirstRightPinnedColumn + ? '4px 0 4px -4px hsl(var(--heroui-border)) inset' + : undefined, + left: isPinned === 'left' ? `${column.getStart('left')}px` : undefined, + right: isPinned === 'right' ? `${column.getAfter('right')}px` : undefined, + position: isPinned ? 'sticky' : 'relative', + borderRight: isLastLeftPinnedColumn + ? '1px solid hsl(var(--heroui-border))' + : undefined, + borderLeft: isFirstRightPinnedColumn + ? '1px solid hsl(var(--heroui-border))' + : undefined, + background: isSelected + ? 'hsl(var(--heroui-primary) / 0.12)' + : isPinned + ? 'hsl(var(--heroui-background))' + : undefined, + zIndex: isPinned ? 2 : 0, + } +} + +function DepartmentPill({ department }: { department: Person['department'] }) { + return ( + + + {department.slice(0, 2).toUpperCase()} + + {toSentenceCase(department)} + + ) +} + +function EllipsisText({ children }: { children: React.ReactNode }) { + return {children} +} + +function StatusBadge({ status }: { status: Person['status'] }) { + const color: Record = { + active: 'success', + inactive: 'danger', + pending: 'warning', + } + + return ( + + {toSentenceCase(status)} + + ) +} + +function RowActions({ person }: { person: Person }) { + return ( + + ••• + + + { + void navigator.clipboard.writeText(person.id) + }} + > + Copy ID + + View details + View profile + + + + ) +} + +function SortIcon({ direction }: { direction: 'asc' | 'desc' | undefined }) { + if (direction === 'asc') return + if (direction === 'desc') return + return ( + + ) +} + +function ColumnHeaderMenu({ + column, + title, +}: { + column: AppColumn + title: string +}) { + const canSort = column.getCanSort() + const canHide = column.getCanHide() + const canPin = column.getCanPin() + const canGroup = column.getCanGroup() + const sorting = React.useContext(SortingContext) + const direction = canSort ? getSortDirection(sorting, column.id) : undefined + const pinned = canPin ? column.getIsPinned() : false + const grouped = canGroup ? column.getIsGrouped() : false + + if (!canSort && !canHide && !canPin && !canGroup) { + return {title} + } + + return ( +
+ {canSort ? ( + + ) : ( + {title} + )} + + + ▾ + + + + {canSort ? ( + <> + column.toggleSorting(false)} + > + Asc + + column.toggleSorting(true)} + > + Desc + + + ) : null} + {canGroup ? ( + + {grouped ? 'Ungroup' : 'Group by'} + + ) : null} + {canPin ? ( + <> + column.pin('left')} + > + Pin left + + column.pin('right')} + > + Pin right + + {pinned ? ( + column.pin(false)}> + Unpin + + ) : null} + + ) : null} + {canHide ? ( + column.toggleVisibility(false)} + > + Hide + + ) : null} + + + +
+ ) +} + +function HeroSelect({ + label, + value, + options, + className, + showLabel = true, + onChange, +}: { + label: string + value: string | null + options: Array<{ value: string; label: string }> + className?: string + showLabel?: boolean + onChange: (value: string) => void +}) { + return ( + + ) +} + +function ViewOptionsPopover({ + table, + columnOrder, + onColumnOrderChange, +}: { + table: AppTable + columnOrder: Array + onColumnOrderChange: React.Dispatch>> +}) { + const [query, setQuery] = React.useState('') + const sensors = useSensors( + useSensor(PointerSensor, { activationConstraint: { distance: 6 } }), + ) + const columns = table + .getAllColumns() + .filter((column) => typeof column.accessorFn !== 'undefined') + .sort((a, b) => columnOrder.indexOf(a.id) - columnOrder.indexOf(b.id)) + .filter((column) => + (column.columnDef.meta?.label ?? column.id) + .toLowerCase() + .includes(query.toLowerCase()), + ) + + const onDragEnd = (event: DragEndEvent) => { + const { active, over } = event + if (!over || active.id === over.id) return + + onColumnOrderChange((current) => { + const oldIndex = current.indexOf(String(active.id)) + const newIndex = current.indexOf(String(over.id)) + return oldIndex >= 0 && newIndex >= 0 + ? arrayMove(current, oldIndex, newIndex) + : current + }) + } + + return ( + + + + + setQuery(event.currentTarget.value)} + /> + + column.id)} + strategy={verticalListSortingStrategy} + > +
+ {columns.map((column) => ( + +
+ + column.toggleVisibility(selected) + } + > + {column.columnDef.meta?.label ?? column.id} + + +
+
+ ))} +
+
+
+
+
+
+ ) +} + +function SortListPopover({ + table, + sorting, + onSortingChange, +}: { + table: AppTable + sorting: SortingState + onSortingChange: React.Dispatch> +}) { + const sensors = useSensors( + useSensor(PointerSensor, { activationConstraint: { distance: 6 } }), + ) + const sortableColumns = table + .getAllColumns() + .filter((column) => column.getCanSort()) + const columnOptions = sortableColumns.map((column) => ({ + value: column.id, + label: column.columnDef.meta?.label ?? column.id, + })) + + const updateSort = (index: number, patch: Partial) => { + onSortingChange((current) => + current.map((sort, sortIndex) => + sortIndex === index ? { ...sort, ...patch } : sort, + ), + ) + } + + const addSort = () => { + const nextColumn = sortableColumns.find( + (column) => !sorting.some((sort) => sort.id === column.id), + ) + if (nextColumn) { + onSortingChange((current) => [ + ...current, + { id: nextColumn.id, desc: false }, + ]) + } + } + + const onDragEnd = (event: DragEndEvent) => { + const { active, over } = event + if (!over || active.id === over.id) return + + onSortingChange((current) => { + const oldIndex = current.findIndex((sort) => sort.id === active.id) + const newIndex = current.findIndex((sort) => sort.id === over.id) + return oldIndex >= 0 && newIndex >= 0 + ? arrayMove(current, oldIndex, newIndex) + : current + }) + } + + return ( + + + + +
+ {sorting.length ? 'Sort by' : 'No sorting applied'} +
+ + sort.id)} + strategy={verticalListSortingStrategy} + > +
+ {sorting.map((sort, index) => ( + +
+ + updateSort(index, { id: value })} + /> + + updateSort(index, { desc: value === 'desc' }) + } + /> + +
+
+ ))} +
+
+
+
+ + +
+
+
+
+ ) +} + +function FilterValueInput({ + column, + filter, + onFilterUpdate, +}: { + column: AppColumn + filter: ExtendedColumnFilter + onFilterUpdate: ( + filterId: string, + patch: Partial, + ) => void +}) { + if (!filter.filterId) return null + const variant = column.columnDef.meta?.variant ?? 'text' + const operator = filter.operator ?? 'includesString' + const disabled = operator === 'isEmpty' || operator === 'isNotEmpty' + + if (disabled) + return
No value required
+ + if (variant === 'select') { + const options = column.columnDef.meta?.options ?? [] + return ( + onFilterUpdate(filter.filterId!, { value })} + /> + ) + } + + if (variant === 'multi-select') { + const options = column.columnDef.meta?.options ?? [] + const values = Array.isArray(filter.value) + ? filter.value.map(String) + : typeof filter.value === 'string' && filter.value + ? [filter.value] + : [] + return ( + + ) + } + + if (variant === 'date') { + if (operator === 'inRange') { + const value = Array.isArray(filter.value) ? filter.value : [] + return ( +
+ + onFilterUpdate(filter.filterId!, { + value: [ + event.currentTarget.value + ? new Date(event.currentTarget.value).toISOString() + : undefined, + value[1], + ], + }) + } + /> + + onFilterUpdate(filter.filterId!, { + value: [ + value[0], + event.currentTarget.value + ? new Date(event.currentTarget.value).toISOString() + : undefined, + ], + }) + } + /> +
+ ) + } + + return ( + + onFilterUpdate(filter.filterId!, { + value: event.currentTarget.value + ? new Date(event.currentTarget.value).toISOString() + : undefined, + }) + } + /> + ) + } + + if (variant === 'number') { + return ( + + onFilterUpdate(filter.filterId!, { + value: + event.currentTarget.value === '' + ? '' + : Number(event.currentTarget.value), + }) + } + /> + ) + } + + return ( + + onFilterUpdate(filter.filterId!, { value: event.currentTarget.value }) + } + /> + ) +} + +function FilterListPopover({ + table, + columnFilters, + onColumnFiltersChange, +}: { + table: AppTable + columnFilters: Array + onColumnFiltersChange: React.Dispatch< + React.SetStateAction> + > +}) { + const filterableColumns = table + .getAllColumns() + .filter((column) => column.getCanFilter()) + const fieldOptions = filterableColumns.map((column) => ({ + value: column.id, + label: column.columnDef.meta?.label ?? column.id, + })) + + const updateFilter = ( + filterId: string, + patch: Partial, + ) => { + onColumnFiltersChange((current) => + current.map((filter) => + filter.filterId === filterId ? { ...filter, ...patch } : filter, + ), + ) + } + + const addFilter = () => { + const [column] = filterableColumns + if (!column) return + onColumnFiltersChange((current) => [ + ...current, + { + id: column.id, + filterId: crypto.randomUUID(), + value: '', + operator: 'includesString', + joinOperator: current[0]?.joinOperator ?? 'and', + }, + ]) + } + + return ( + + + + +
Filters
+ {columnFilters.map((filter, index) => { + const column = table.getColumn(filter.id) + if (!column || !filter.filterId) return null + const variant = column.columnDef.meta?.variant ?? 'text' + const operators = getFilterOperators(variant) + return ( +
+ {index === 0 ? ( +
Where
+ ) : index === 1 ? ( + + onColumnFiltersChange((current) => + current.map((item) => ({ + ...item, + joinOperator: joinOperator as 'and' | 'or', + })), + ) + } + /> + ) : ( +
+ {filter.joinOperator ?? 'and'} +
+ )} + { + const nextColumn = table.getColumn(nextColumnId) + if (nextColumn) { + updateFilter(filter.filterId!, { + id: nextColumn.id, + operator: getFilterOperators( + nextColumn.columnDef.meta?.variant ?? 'text', + )[0].value, + value: '', + }) + } + }} + /> + ({ + value: operator.value, + label: operator.label, + }))} + onChange={(operator) => + updateFilter(filter.filterId!, { + operator: operator as ExtendedColumnFilter['operator'], + value: '', + }) + } + /> + + +
+ ) + })} +
+ + +
+
+
+
+ ) +} + +function Pagination({ table }: { table: AppTable }) { + const pageIndex = table.store.state.pagination.pageIndex + const pageSize = table.store.state.pagination.pageSize + const pageItems = getPageItems(pageIndex, table.getPageCount()) + + return ( +
+
+ {table.getFilteredSelectedRowModel().rows.length.toLocaleString()} of{' '} + {table.getFilteredRowModel().rows.length.toLocaleString()} row(s) + selected. +
+
+ Rows per page: + ({ + value, + label: value, + }))} + onChange={(value) => { + table.setPageSize(Number(value)) + table.setPageIndex(0) + }} + /> + + + + + table.previousPage()} + > + + Prev + + + {pageItems.map((page, index) => + page === 'ellipsis' ? ( + + + + ) : ( + + table.setPageIndex(page - 1)} + > + {page} + + + ), + )} + + table.nextPage()} + > + Next + + + + + + +
+
+ ) +} + +function ModeSwitch() { + const { theme, setTheme } = useTheme('system') + + return ( + + setTheme(selected ? 'dark' : 'light')} + > + + + + + Theme + + ) +} + +function DebouncedTextInput({ + value: initialValue, + onChange, + debounce = 300, + ...props +}: { + value: string | number + onChange: (value: string | number) => void + debounce?: number +} & Omit, 'onChange'>) { + const [value, setValue] = React.useState(initialValue) + + React.useEffect(() => { + setValue(initialValue) + }, [initialValue]) + + const debouncedOnChange = useDebouncedCallback(onChange, { wait: debounce }) + + return ( + { + setValue(event.currentTarget.value) + debouncedOnChange(event.currentTarget.value) + }} + /> + ) +} + +function App() { + const rerender = React.useReducer(() => ({}), {})[1] + const [rowSelection, setRowSelection] = React.useState({}) + const [sorting, setSorting] = React.useState([]) + const [columnFilters, setColumnFilters] = React.useState< + Array + >([]) + const [columnVisibility, setColumnVisibility] = React.useState({}) + const [columnSizing, setColumnSizing] = React.useState({}) + const [globalFilter, setGlobalFilter] = React.useState('') + const [columnPinning, setColumnPinning] = React.useState({ + left: ['select'], + right: ['actions'], + }) + const [grouping, setGrouping] = React.useState([]) + const [expanded, setExpanded] = React.useState({}) + const [data, setData] = React.useState(() => makeData(1_000)) + + const columns = React.useMemo( + () => + columnHelper.columns([ + columnHelper.display({ + id: 'select', + header: ({ table }) => ( + table.toggleAllPageRowsSelected(selected)} + aria-label="Select all" + /> + ), + cell: ({ row }) => ( + row.toggleSelected(selected)} + aria-label="Select row" + /> + ), + maxSize: 48, + enableSorting: false, + enableHiding: false, + enableResizing: false, + }), + columnHelper.accessor('firstName', { + id: 'firstName', + header: ({ column }) => ( + + ), + cell: (info) => ( + {String(info.getValue())} + ), + meta: { label: 'First Name', variant: 'text' }, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + header: ({ column }) => ( + + ), + cell: (info) => ( + {String(info.getValue())} + ), + meta: { label: 'Last Name', variant: 'text' }, + }), + columnHelper.accessor('age', { + id: 'age', + header: ({ column }) => ( + + ), + cell: (info) => ( + {String(info.getValue())} + ), + aggregationFn: 'mean', + aggregatedCell: ({ getValue }) => ( + + Avg: {Math.round(Number(getValue()) * 10) / 10} + + ), + meta: { label: 'Age', variant: 'number' }, + }), + columnHelper.accessor('email', { + id: 'email', + header: ({ column }) => ( + + ), + cell: (info) => ( + {info.cell.getValue()} + ), + meta: { label: 'Email', variant: 'text' }, + }), + columnHelper.accessor('status', { + id: 'status', + header: ({ column }) => ( + + ), + cell: (info) => { + const status = info.getValue() + return status ? : null + }, + aggregatedCell: () => null, + meta: { + label: 'Status', + variant: 'select', + options: statuses.map((status) => ({ + label: toSentenceCase(status), + value: status, + })), + }, + }), + columnHelper.accessor('department', { + id: 'department', + header: ({ column }) => ( + + ), + cell: (info) => { + const department = info.getValue() + return department ? ( + + ) : null + }, + aggregatedCell: () => null, + meta: { + label: 'Department', + variant: 'multi-select', + options: departments.map((department) => ({ + label: toSentenceCase(department), + value: department, + })), + }, + }), + columnHelper.accessor('joinDate', { + id: 'joinDate', + header: ({ column }) => ( + + ), + cell: (info) => formatDate(info.getValue()), + aggregationFn: 'min', + aggregatedCell: ({ getValue }) => { + const earliest = getValue() + return ( + + Earliest: {earliest ? formatDate(earliest) : '-'} + + ) + }, + meta: { label: 'Join Date', variant: 'date' }, + }), + columnHelper.accessor((row) => row.age, { + id: 'progress', + header: ({ column }) => ( + + ), + cell: (info) => { + const value = Math.min(100, Math.max(0, Number(info.getValue()))) + return ( + + + + + + ) + }, + meta: { label: 'Profile Progress', variant: 'number' }, + }), + columnHelper.display({ + id: 'actions', + enableHiding: false, + cell: ({ row }) => , + maxSize: 60, + enableResizing: false, + }), + ]), + [], + ) + + const [columnOrder, setColumnOrder] = React.useState>(() => + columns.map((column) => column.id ?? ''), + ) + + const table = useTable( + { + _features, + _rowModels: { + coreRowModel: createCoreRowModel(), + filteredRowModel: createFilteredRowModel({ + ...filterFns, + fuzzy: fuzzyFilter, + }), + facetedRowModel: createFacetedRowModel(), + facetedUniqueValues: createFacetedUniqueValues(), + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + groupedRowModel: createGroupedRowModel(aggregationFns), + expandedRowModel: createExpandedRowModel(), + }, + columns, + data, + defaultColumn: { + minSize: 60, + maxSize: 800, + filterFn: dynamicFilterFn, + }, + globalFilterFn: 'fuzzy', + state: { + rowSelection, + sorting, + columnVisibility, + columnOrder, + columnSizing, + columnFilters, + globalFilter, + columnPinning, + grouping, + expanded, + }, + onSortingChange: setSorting, + onColumnVisibilityChange: setColumnVisibility, + onColumnOrderChange: setColumnOrder, + onColumnSizingChange: setColumnSizing, + onColumnFiltersChange: setColumnFilters, + onGlobalFilterChange: setGlobalFilter, + onColumnPinningChange: setColumnPinning, + onGroupingChange: setGrouping, + onExpandedChange: setExpanded, + getRowId: (row) => row.id, + enableRowSelection: true, + onRowSelectionChange: setRowSelection, + columnResizeMode: 'onChange', + debugTable: true, + }, + (state) => state, // default selector + ) + + const columnSizeVars = React.useMemo(() => { + const headers = table.getFlatHeaders() + const colSizes: Record = {} + for (const header of headers) { + colSizes[`--header-${header.id}-size`] = header.getSize() + colSizes[`--col-${header.column.id}-size`] = header.column.getSize() + } + return colSizes + }, [table.store.state.columnSizing]) + + const refreshData = () => setData(makeData(1_000)) + const stressTest = () => setData(makeData(200_000)) + + return ( + +
+
+ +
+ + + + + +
+
+ +
+ setGlobalFilter(String(value))} + placeholder="Search all columns..." + /> + + + +
+ + + + + + {table + .getHeaderGroups()[0] + ?.headers.filter((header) => header.column.getIsVisible()) + .map((header) => ( + + ))} + + + {table.getRowModel().rows.map((row) => { + const selected = row.getIsSelected() + return ( + + {row.getVisibleCells().map((cell) => ( + + {cell.getIsGrouped() ? ( + + ) : ( + + )} + + ))} + + ) + })} + + + + + +
+
+
+ ) +} + +function ResizableHeaderCell({ + header, + table, +}: { + header: Header + table: { + FlexRender: React.ComponentType<{ + header: Header + }> + } +}) { + const sorting = React.useContext(SortingContext) + const sortDirection = getSortDirection(sorting, header.column.id) + + return ( + +
+ {header.isPlaceholder ? null : } + {header.column.getCanResize() ? ( +
header.column.resetSize()} + onMouseDown={header.getResizeHandler()} + onTouchStart={header.getResizeHandler()} + className={cn( + 'absolute right-[-6px] top-0 h-full w-1.5 cursor-col-resize touch-none', + header.column.getIsResizing() && 'bg-primary', + )} + /> + ) : null} +
+ + ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +ReactDOM.createRoot(rootElement).render( + + + , +) diff --git a/examples/react/kitchen-sink-hero-ui/src/styles/globals.css b/examples/react/kitchen-sink-hero-ui/src/styles/globals.css new file mode 100644 index 0000000000..0192db8019 --- /dev/null +++ b/examples/react/kitchen-sink-hero-ui/src/styles/globals.css @@ -0,0 +1,6 @@ +@import 'tailwindcss' source('../../'); +@import '@heroui/styles'; + +body { + margin: 0; +} diff --git a/examples/react/kitchen-sink-hero-ui/src/types/index.ts b/examples/react/kitchen-sink-hero-ui/src/types/index.ts new file mode 100644 index 0000000000..4958d1736a --- /dev/null +++ b/examples/react/kitchen-sink-hero-ui/src/types/index.ts @@ -0,0 +1,38 @@ +import type { + ColumnFilter, + TableFeatures, + filterFns, +} from '@tanstack/react-table' + +export type TableFilterFeatures = Pick< + TFeatures, + 'columnFilteringFeature' | 'columnFacetingFeature' +> + +export type FilterOperator = + | keyof typeof filterFns + | 'notIncludesString' + | 'notEqualsString' + | 'notEquals' + | 'greaterThan' + | 'notGreaterThan' + | 'greaterThanOrEqualTo' + | 'notGreaterThanOrEqualTo' + | 'lessThan' + | 'notLessThan' + | 'lessThanOrEqualTo' + | 'notLessThanOrEqualTo' + | 'isRelativeToToday' + | 'inRange' + | 'startsWith' + | 'endsWith' + | 'isEmpty' + | 'isNotEmpty' + +export type JoinOperator = 'and' | 'or' + +export interface ExtendedColumnFilter extends ColumnFilter { + filterId?: string + operator?: FilterOperator + joinOperator?: JoinOperator +} diff --git a/examples/react/kitchen-sink-hero-ui/src/vite-env.d.ts b/examples/react/kitchen-sink-hero-ui/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/kitchen-sink-hero-ui/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/kitchen-sink-hero-ui/tsconfig.json b/examples/react/kitchen-sink-hero-ui/tsconfig.json new file mode 100644 index 0000000000..6714dcfb68 --- /dev/null +++ b/examples/react/kitchen-sink-hero-ui/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "paths": { + "@/*": ["./src/*"] + }, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/react/kitchen-sink-hero-ui/vite.config.js b/examples/react/kitchen-sink-hero-ui/vite.config.js new file mode 100644 index 0000000000..27dc86dd10 --- /dev/null +++ b/examples/react/kitchen-sink-hero-ui/vite.config.js @@ -0,0 +1,31 @@ +import path from 'node:path' +import { defineConfig } from 'vite' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' +import rollupReplace from '@rollup/plugin-replace' +import tailwindcss from '@tailwindcss/vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), + tailwindcss(), + ], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, +}) diff --git a/examples/react/kitchen-sink-mantine/.gitignore b/examples/react/kitchen-sink-mantine/.gitignore new file mode 100644 index 0000000000..18a5070e2a --- /dev/null +++ b/examples/react/kitchen-sink-mantine/.gitignore @@ -0,0 +1,6 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local +!lib diff --git a/examples/react/kitchen-sink-mantine/README.md b/examples/react/kitchen-sink-mantine/README.md new file mode 100644 index 0000000000..d41982d282 --- /dev/null +++ b/examples/react/kitchen-sink-mantine/README.md @@ -0,0 +1,6 @@ +# React Kitchen Sink Mantine Example + +To run this example: + +- `pnpm install` +- `pnpm start` diff --git a/examples/react/kitchen-sink-mantine/index.html b/examples/react/kitchen-sink-mantine/index.html new file mode 100644 index 0000000000..1551290f72 --- /dev/null +++ b/examples/react/kitchen-sink-mantine/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/react/kitchen-sink-mantine/package.json b/examples/react/kitchen-sink-mantine/package.json new file mode 100644 index 0000000000..f0861cef50 --- /dev/null +++ b/examples/react/kitchen-sink-mantine/package.json @@ -0,0 +1,41 @@ +{ + "name": "tanstack-react-table-example-kitchen-sink-mantine", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", + "@faker-js/faker": "^10.4.0", + "@mantine/core": "^9.1.1", + "@mantine/hooks": "^9.1.1", + "@tabler/icons-react": "^3.44.0", + "@tanstack/match-sorter-utils": "^9.0.0-alpha.4", + "@tanstack/react-pacer": "^0.22.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "date-fns": "^4.1.0", + "react": "^19.2.6", + "react-dom": "^19.2.6" + }, + "devDependencies": { + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "postcss": "^8.5.14", + "postcss-preset-mantine": "^1.18.0", + "postcss-simple-vars": "^7.0.1", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/react/kitchen-sink-mantine/postcss.config.cjs b/examples/react/kitchen-sink-mantine/postcss.config.cjs new file mode 100644 index 0000000000..6a683623b3 --- /dev/null +++ b/examples/react/kitchen-sink-mantine/postcss.config.cjs @@ -0,0 +1,14 @@ +module.exports = { + plugins: { + 'postcss-preset-mantine': {}, + 'postcss-simple-vars': { + variables: { + 'mantine-breakpoint-xs': '36em', + 'mantine-breakpoint-sm': '48em', + 'mantine-breakpoint-md': '62em', + 'mantine-breakpoint-lg': '75em', + 'mantine-breakpoint-xl': '88em', + }, + }, + }, +} diff --git a/examples/react/kitchen-sink-mantine/src/lib/data-table.ts b/examples/react/kitchen-sink-mantine/src/lib/data-table.ts new file mode 100644 index 0000000000..24568c1a9d --- /dev/null +++ b/examples/react/kitchen-sink-mantine/src/lib/data-table.ts @@ -0,0 +1,502 @@ +import { + filterFn_arrIncludes, + filterFn_equals, + filterFn_equalsString, + filterFn_greaterThan, + filterFn_greaterThanOrEqualTo, + filterFn_includesString, + filterFn_lessThan, + filterFn_lessThanOrEqualTo, +} from '@tanstack/react-table' +import { rankItem } from '@tanstack/match-sorter-utils' +import type { + ExtendedColumnFilter, + FilterOperator, + JoinOperator, + TableFilterFeatures, +} from '@/types' +import type { RankingInfo } from '@tanstack/match-sorter-utils' +import type { + FilterFn, + Row, + RowData, + TableFeatures, +} from '@tanstack/react-table' + +/** + * Fuzzy filter using @tanstack/match-sorter-utils. Used as the global filter + * (`globalFilterFn: 'fuzzy'`) in the kitchen-sink example so the toolbar + * search ranks rows by best match across all columns. + * + * Mirrors the canonical pattern from `examples/react/filters-fuzzy`. Written + * as a plain function (not a typed const) so it stays generic over TFeatures + * and TData and can be slotted into any `createFilteredRowModel({...})` + * registration without narrowing inference. + */ +export function fuzzyFilter< + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + value: string, + addMeta?: (meta: { itemRank: RankingInfo }) => void, +): boolean { + // Rank the item + const itemRank = rankItem(row.getValue(columnId), value) + + // Store the itemRank info so a fuzzy sort function (if any) could reuse it + addMeta?.({ itemRank }) + + // Return whether the item should be filtered in/out + return itemRank.passed +} + +declare module '@tanstack/react-table' { + interface FilterFns { + fuzzy: FilterFn + } + interface FilterMeta { + itemRank?: RankingInfo + } +} + +function isFalsy(val: unknown) { + return ( + val === undefined || + val === null || + val === '' || + (Array.isArray(val) && val.length === 0) + ) +} + +function isValidDate(value: unknown): boolean { + if (value instanceof Date) return !isNaN(value.getTime()) + if (typeof value === 'string') return !isNaN(Date.parse(value)) + return false +} + +function toDate(value: unknown): Date | null { + if (value instanceof Date) return value + if (typeof value === 'string') { + const date = new Date(value) + return !isNaN(date.getTime()) ? date : null + } + return null +} + +function isSameDay(date1: Date, date2: Date): boolean { + const date1Str = date1.toISOString().split('T')[0] + const date2Str = date2.toISOString().split('T')[0] + return date1Str === date2Str +} + +const filterFn_enhancedEquals: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + filterValue: unknown, +) => { + const rowValue = row.getValue(columnId) + + if (Array.isArray(filterValue)) { + return filterFn_arrIncludes(row, columnId, filterValue) + } + + if (isValidDate(rowValue) && isValidDate(filterValue)) { + const rowDate = toDate(rowValue) + const filterDate = toDate(filterValue) + + if (rowDate && filterDate) { + return isSameDay(rowDate, filterDate) + } + } + + return filterFn_equals(row, columnId, filterValue) +} + +filterFn_enhancedEquals.resolveFilterValue = (val: any) => isFalsy(val) + +const filterFn_enhancedGreaterThan: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + filterValue: unknown, +) => { + const rowValue = row.getValue(columnId) + + if (isValidDate(rowValue) && isValidDate(filterValue)) { + const rowDate = toDate(rowValue) + const filterDate = toDate(filterValue) + + if (rowDate && filterDate) { + return rowDate.getTime() > filterDate.getTime() + } + } + + return filterFn_greaterThan(row, columnId, filterValue) +} + +filterFn_enhancedGreaterThan.resolveFilterValue = (val: any) => isFalsy(val) + +const filterFn_enhancedGreaterThanOrEqualTo: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + filterValue: unknown, +) => { + const rowValue = row.getValue(columnId) + + if (isValidDate(rowValue) && isValidDate(filterValue)) { + const rowDate = toDate(rowValue) + const filterDate = toDate(filterValue) + + if (rowDate && filterDate) { + return rowDate.getTime() >= filterDate.getTime() + } + } + + return filterFn_greaterThanOrEqualTo(row, columnId, filterValue) +} + +filterFn_enhancedGreaterThanOrEqualTo.resolveFilterValue = (val: any) => + isFalsy(val) + +const filterFn_enhancedLessThan: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + filterValue: unknown, +) => { + const rowValue = row.getValue(columnId) + + if (isValidDate(rowValue) && isValidDate(filterValue)) { + const rowDate = toDate(rowValue) + const filterDate = toDate(filterValue) + + if (rowDate && filterDate) { + return rowDate.getTime() < filterDate.getTime() + } + } + + return filterFn_lessThan(row, columnId, filterValue) +} + +filterFn_enhancedLessThan.resolveFilterValue = (val: any) => isFalsy(val) + +const filterFn_enhancedLessThanOrEqualTo: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + filterValue: unknown, +) => { + const rowValue = row.getValue(columnId) + + if (isValidDate(rowValue) && isValidDate(filterValue)) { + const rowDate = toDate(rowValue) + const filterDate = toDate(filterValue) + + if (rowDate && filterDate) { + return rowDate.getTime() <= filterDate.getTime() + } + } + + return filterFn_lessThanOrEqualTo(row, columnId, filterValue) +} + +filterFn_enhancedLessThanOrEqualTo.resolveFilterValue = (val: any) => + isFalsy(val) + +const filterFn_startsWith: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + filterValue: string, +) => { + const value = String(row.getValue(columnId) ?? '').toLowerCase() + return value.startsWith(filterValue.toLowerCase()) +} + +filterFn_startsWith.resolveFilterValue = (val: any) => isFalsy(val) + +const filterFn_endsWith: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + filterValue: string, +) => { + const value = String(row.getValue(columnId) ?? '').toLowerCase() + return value.endsWith(filterValue.toLowerCase()) +} + +filterFn_endsWith.resolveFilterValue = (val: any) => isFalsy(val) + +const filterFn_isEmpty: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, +) => { + const value = row.getValue(columnId) + return ( + value === undefined || + value === null || + value === '' || + (Array.isArray(value) && value.length === 0) + ) +} + +filterFn_isEmpty.resolveFilterValue = (val: any) => isFalsy(val) + +const filterFn_inBetween: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + filterValue: unknown, +) => { + if (Array.isArray(filterValue)) { + const [min, max] = filterValue + const rowValue: unknown = row.getValue(columnId) + + if (min === undefined || min === '' || min === null) { + return max === undefined || max === '' || max === null + ? true + : typeof rowValue === 'number' && typeof max === 'number' + ? rowValue <= max + : String(rowValue) <= String(max) + } + if (max === undefined || max === '' || max === null) { + return typeof rowValue === 'number' && typeof min === 'number' + ? rowValue >= min + : String(rowValue) >= String(min) + } + + if ( + rowValue instanceof Date || + (typeof rowValue === 'string' && !isNaN(Date.parse(rowValue))) + ) { + const dateValue = new Date(rowValue).getTime() + const minDate = new Date(min as string | Date).getTime() + const maxDate = new Date(max as string | Date).getTime() + return dateValue >= minDate && dateValue <= maxDate + } + + const numValue = Number(rowValue) + return ( + !isNaN(numValue) && numValue >= Number(min) && numValue <= Number(max) + ) + } + return true +} + +filterFn_inBetween.autoRemove = (val: any) => isFalsy(val) + +const filterFn_isRelativeToToday: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + filterValue: unknown, +) => { + const rowValue = row.getValue(columnId) + + if (!isValidDate(rowValue)) return false + + const rowDate = toDate(rowValue) + if (!rowDate) return false + + rowDate.setHours(0, 0, 0, 0) + + const today = new Date() + today.setHours(0, 0, 0, 0) + + const diffInDays = Math.floor( + (rowDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24), + ) + + if (typeof filterValue === 'number') { + return diffInDays === filterValue + } else if (typeof filterValue === 'string') { + const numValue = parseInt(filterValue, 10) + if (!isNaN(numValue)) { + return diffInDays === numValue + } + } else if (Array.isArray(filterValue) && filterValue.length === 2) { + const [min, max] = filterValue + const minDays = typeof min === 'number' ? min : parseInt(min as string, 10) + const maxDays = typeof max === 'number' ? max : parseInt(max as string, 10) + + if (!isNaN(minDays) && !isNaN(maxDays)) { + return diffInDays >= minDays && diffInDays <= maxDays + } + } + + return false +} + +filterFn_isRelativeToToday.autoRemove = (val: any) => isFalsy(val) + +export const dynamicFilterFn: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, TData>, + columnId: string, + filterValue: unknown, +) => { + let operator: FilterOperator = 'includesString' + let value = filterValue + let joinOperator: JoinOperator = 'and' + + const filters: Array = + row.table.store.state.columnFilters.filter((f) => f.id === columnId) + + if (!filters.length) return true + + if (filters[0].joinOperator) { + joinOperator = filters[0].joinOperator + } + + return filters.reduce((pass, filter, index) => { + operator = filter.operator ?? 'includesString' + value = filter.value + + if (isFalsy(value) && operator !== 'isEmpty' && operator !== 'isNotEmpty') { + return pass + } + + let result: boolean + switch (operator) { + case 'includesString': + result = filterFn_includesString(row, columnId, value) + break + case 'notIncludesString': + result = !filterFn_includesString(row, columnId, value) + break + case 'equalsString': + result = filterFn_equalsString(row, columnId, value) + break + case 'notEqualsString': + result = !filterFn_equalsString(row, columnId, value) + break + case 'startsWith': + result = filterFn_startsWith(row, columnId, value) + break + case 'endsWith': + result = filterFn_endsWith(row, columnId, value) + break + case 'isEmpty': + result = filterFn_isEmpty(row, columnId, '') + break + case 'isNotEmpty': + result = !filterFn_isEmpty(row, columnId, '') + break + case 'equals': + result = filterFn_enhancedEquals(row, columnId, value) + break + case 'notEquals': + result = !filterFn_enhancedEquals(row, columnId, value) + break + case 'greaterThan': + result = filterFn_enhancedGreaterThan(row, columnId, value) + break + case 'greaterThanOrEqualTo': + result = filterFn_enhancedGreaterThanOrEqualTo(row, columnId, value) + break + case 'lessThan': + result = filterFn_enhancedLessThan(row, columnId, value) + break + case 'lessThanOrEqualTo': + result = filterFn_enhancedLessThanOrEqualTo(row, columnId, value) + break + case 'inRange': + result = filterFn_inBetween(row, columnId, value) + break + case 'isRelativeToToday': + result = filterFn_isRelativeToToday(row, columnId, value) + break + default: + result = filterFn_includesString(row, columnId, value) + break + } + + if (index === 0) return result + + return joinOperator === 'and' ? pass && result : pass || result + }, true) +} + +export function getFilterOperators(type: string): Array<{ + label: string + value: FilterOperator +}> { + switch (type) { + case 'text': + return [ + { label: 'contains', value: 'includesString' }, + { label: 'does not contain', value: 'notIncludesString' }, + { label: 'starts with', value: 'startsWith' }, + { label: 'ends with', value: 'endsWith' }, + { label: 'is', value: 'equalsString' }, + { label: 'is not', value: 'notEqualsString' }, + { label: 'is empty', value: 'isEmpty' }, + { label: 'is not empty', value: 'isNotEmpty' }, + ] + case 'number': + return [ + { label: 'is', value: 'equals' }, + { label: 'is not', value: 'notEquals' }, + { label: 'is less than', value: 'lessThan' }, + { label: 'is less than or equal to', value: 'lessThanOrEqualTo' }, + { label: 'is greater than', value: 'greaterThan' }, + { label: 'is greater than or equal to', value: 'greaterThanOrEqualTo' }, + { label: 'is between', value: 'inRange' }, + ] + case 'date': + return [ + { label: 'is', value: 'equals' }, + { label: 'is not', value: 'notEquals' }, + { label: 'is before', value: 'lessThan' }, + { label: 'is on or before', value: 'lessThanOrEqualTo' }, + { label: 'is after', value: 'greaterThan' }, + { label: 'is on or after', value: 'greaterThanOrEqualTo' }, + { label: 'is between', value: 'inRange' }, + { label: 'is relative to today', value: 'isRelativeToToday' }, + { label: 'is empty', value: 'isEmpty' }, + { label: 'is not empty', value: 'isNotEmpty' }, + ] + case 'select': + case 'multi-select': + return [ + { label: 'is', value: 'equals' }, + { label: 'is not', value: 'notEquals' }, + { label: 'is empty', value: 'isEmpty' }, + { label: 'is not empty', value: 'isNotEmpty' }, + ] + default: + return [ + { label: 'contains', value: 'includesString' }, + { label: 'does not contain', value: 'notIncludesString' }, + { label: 'is', value: 'equalsString' }, + { label: 'is not', value: 'notEqualsString' }, + ] + } +} diff --git a/examples/react/kitchen-sink-mantine/src/lib/make-data.ts b/examples/react/kitchen-sink-mantine/src/lib/make-data.ts new file mode 100644 index 0000000000..8198ee2581 --- /dev/null +++ b/examples/react/kitchen-sink-mantine/src/lib/make-data.ts @@ -0,0 +1,57 @@ +import { faker } from '@faker-js/faker' + +export const statuses = ['active', 'inactive', 'pending'] as const +export const departments = [ + 'engineering', + 'marketing', + 'finance', + 'sales', + 'hr', +] as const + +export interface Person { + id: string + firstName: string + lastName: string + age: number + email: string + status: (typeof statuses)[number] + department: (typeof departments)[number] + joinDate: Date + subRows?: Array +} + +function range(len: number) { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +function newPerson(): Person { + return { + id: faker.string.uuid(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int({ min: 20, max: 65 }), + email: faker.internet.email(), + status: faker.helpers.arrayElement(statuses), + department: faker.helpers.arrayElement(departments), + joinDate: faker.date.past({ years: 5 }), + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/react/kitchen-sink-mantine/src/main.tsx b/examples/react/kitchen-sink-mantine/src/main.tsx new file mode 100644 index 0000000000..4e45e078b7 --- /dev/null +++ b/examples/react/kitchen-sink-mantine/src/main.tsx @@ -0,0 +1,1675 @@ +'use client' + +import * as React from 'react' +import * as ReactDOM from 'react-dom/client' +import { useDebouncedCallback } from '@tanstack/react-pacer/debouncer' +import { + DndContext, + PointerSensor, + closestCenter, + useSensor, + useSensors, +} from '@dnd-kit/core' +import { + SortableContext, + arrayMove, + useSortable, + verticalListSortingStrategy, +} from '@dnd-kit/sortable' +import { CSS } from '@dnd-kit/utilities' +import { + ActionIcon, + Badge, + Box, + Button, + Checkbox, + Container, + Group, + Pagination as MantinePagination, + MantineProvider, + Table as MantineTable, + Menu, + MultiSelect, + Paper, + Popover, + Progress, + Select, + Stack, + Text, + TextInput, + Tooltip, + UnstyledButton, + useComputedColorScheme, + useMantineColorScheme, +} from '@mantine/core' +import '@mantine/core/styles.css' +import { + IconArrowDown, + IconArrowUp, + IconArrowsSort, + IconBriefcase, + IconBuildingStore, + IconCategory, + IconCheck, + IconChevronDown, + IconChevronLeft, + IconChevronRight, + IconChevronsLeft, + IconChevronsRight, + IconCode, + IconCreditCard, + IconDeviceDesktop, + IconDotsVertical, + IconEyeOff, + IconFilter, + IconGripVertical, + IconMoon, + IconPinned, + IconSearch, + IconSettings, + IconSun, + IconTrash, + IconUsersGroup, +} from '@tabler/icons-react' +import { + aggregationFns, + columnFacetingFeature, + columnFilteringFeature, + columnGroupingFeature, + columnOrderingFeature, + columnPinningFeature, + columnResizingFeature, + columnSizingFeature, + columnVisibilityFeature, + createColumnHelper, + createCoreRowModel, + createExpandedRowModel, + createFacetedRowModel, + createFacetedUniqueValues, + createFilteredRowModel, + createGroupedRowModel, + createPaginatedRowModel, + createSortedRowModel, + filterFns, + globalFilteringFeature, + rowExpandingFeature, + rowPaginationFeature, + rowSelectionFeature, + rowSortingFeature, + sortFns, + tableFeatures, + useTable, +} from '@tanstack/react-table' +import type { Person } from '@/lib/make-data' +import type { DragEndEvent } from '@dnd-kit/core' +import type { + CellData, + Column, + ColumnPinningState, + ColumnSizingState, + ExpandedState, + GroupingState, + Header, + RowData, + SortingState, + Table, + TableFeatures, +} from '@tanstack/react-table' +import type { ExtendedColumnFilter } from '@/types' + +import { + dynamicFilterFn, + fuzzyFilter, + getFilterOperators, +} from '@/lib/data-table' +import { departments, makeData, statuses } from '@/lib/make-data' +import './styles/globals.css' + +declare module '@tanstack/react-table' { + interface ColumnMeta< + TFeatures extends TableFeatures, + TData extends RowData, + TValue extends CellData = CellData, + > { + label?: string + variant?: 'text' | 'number' | 'date' | 'boolean' | 'select' | 'multi-select' + options?: Array<{ label: string; value: string; count?: number }> + } +} + +const _features = tableFeatures({ + rowSortingFeature, + rowPaginationFeature, + rowSelectionFeature, + rowExpandingFeature, + columnFilteringFeature, + columnFacetingFeature, + columnOrderingFeature, + columnVisibilityFeature, + columnSizingFeature, + columnResizingFeature, + columnPinningFeature, + columnGroupingFeature, + globalFilteringFeature, +}) + +const columnHelper = createColumnHelper() +type AppTable = Table +type AppColumn = Column + +function SortableFrame({ + id, + children, +}: { + id: string + children: React.ReactNode +}) { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id }) + + return ( + + {children} + + ) +} + +function toSentenceCase(value: string) { + return value + .replace(/[-_]/g, ' ') + .replace(/\w\S*/g, (word) => word[0].toUpperCase() + word.slice(1)) +} + +function formatDate(value: string) { + return new Intl.DateTimeFormat('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + }).format(new Date(value)) +} + +function toDateInputValue(value: unknown) { + if (!value) return '' + const date = new Date(String(value)) + return Number.isNaN(date.getTime()) ? '' : date.toISOString().slice(0, 10) +} + +function getAriaSort(sortDirection: false | 'asc' | 'desc') { + if (sortDirection === 'asc') return 'ascending' + if (sortDirection === 'desc') return 'descending' + return 'none' +} + +const SortingContext = React.createContext([]) + +function getSortDirection(sorting: SortingState, columnId: string) { + const sort = sorting.find((sort) => sort.id === columnId) + return sort ? (sort.desc ? 'desc' : 'asc') : undefined +} + +function getCommonPinningStyles( + column: AppColumn, + isSelected = false, +): React.CSSProperties { + const isPinned = column.getIsPinned() + const isLastLeftPinnedColumn = + isPinned === 'left' && column.getIsLastColumn('left') + const isFirstRightPinnedColumn = + isPinned === 'right' && column.getIsFirstColumn('right') + + return { + boxShadow: isLastLeftPinnedColumn + ? '-4px 0 4px -4px var(--mantine-color-default-border) inset' + : isFirstRightPinnedColumn + ? '4px 0 4px -4px var(--mantine-color-default-border) inset' + : undefined, + left: isPinned === 'left' ? `${column.getStart('left')}px` : undefined, + right: isPinned === 'right' ? `${column.getAfter('right')}px` : undefined, + position: isPinned ? 'sticky' : 'relative', + borderRight: isLastLeftPinnedColumn + ? '1px solid var(--mantine-color-default-border)' + : undefined, + borderLeft: isFirstRightPinnedColumn + ? '1px solid var(--mantine-color-default-border)' + : undefined, + background: isSelected + ? 'var(--mantine-color-blue-light)' + : isPinned + ? 'var(--mantine-color-body)' + : undefined, + zIndex: isPinned ? 2 : 0, + } +} + +function DepartmentIcon({ department }: { department: Person['department'] }) { + const icons: Record = { + engineering: , + marketing: , + sales: , + hr: , + finance: , + } + + return icons[department] +} + +function DepartmentPill({ department }: { department: Person['department'] }) { + return ( + + + + + + {toSentenceCase(department)} + + + ) +} + +function EllipsisText({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ) +} + +function StatusBadge({ status }: { status: Person['status'] }) { + const color: Record = { + active: 'green', + inactive: 'red', + pending: 'yellow', + } + + return ( + } + > + {toSentenceCase(status)} + + ) +} + +function RowActions({ person }: { person: Person }) { + return ( + + + + + + + + { + void navigator.clipboard.writeText(person.id) + }} + > + Copy ID + + + View details + View profile + + + ) +} + +function SortIcon({ direction }: { direction: 'asc' | 'desc' | undefined }) { + if (direction === 'asc') return + if (direction === 'desc') return + return +} + +function ColumnHeaderMenu({ + column, + title, +}: { + column: AppColumn + title: string +}) { + const canSort = column.getCanSort() + const canHide = column.getCanHide() + const canPin = column.getCanPin() + const canGroup = column.getCanGroup() + const sorting = React.useContext(SortingContext) + const direction = canSort ? getSortDirection(sorting, column.id) : undefined + const pinned = canPin ? column.getIsPinned() : false + const grouped = canGroup ? column.getIsGrouped() : false + + if (!canSort && !canHide && !canPin && !canGroup) { + return {title} + } + + return ( + + {canSort ? ( + + + + {title} + + + + + ) : ( + {title} + )} + + + + + + + + {canSort ? ( + <> + } + onClick={() => column.toggleSorting(false)} + > + Asc + + } + onClick={() => column.toggleSorting(true)} + > + Desc + + + ) : null} + {canGroup ? ( + } + onClick={column.getToggleGroupingHandler()} + > + {grouped ? 'Ungroup' : 'Group by'} + + ) : null} + {canPin ? ( + <> + + } + onClick={() => column.pin('left')} + > + Pin left + + } + onClick={() => column.pin('right')} + > + Pin right + + {pinned ? ( + } + onClick={() => column.pin(false)} + > + Unpin + + ) : null} + + ) : null} + {canHide ? ( + <> + + } + onClick={() => column.toggleVisibility(false)} + > + Hide + + + ) : null} + + + + ) +} + +function ViewOptionsPopover({ + table, + columnOrder, + onColumnOrderChange, +}: { + table: AppTable + columnOrder: Array + onColumnOrderChange: React.Dispatch>> +}) { + const [opened, setOpened] = React.useState(false) + const [query, setQuery] = React.useState('') + const sensors = useSensors( + useSensor(PointerSensor, { activationConstraint: { distance: 6 } }), + ) + const columns = table + .getAllColumns() + .filter((column) => typeof column.accessorFn !== 'undefined') + .sort((a, b) => columnOrder.indexOf(a.id) - columnOrder.indexOf(b.id)) + .filter((column) => + (column.columnDef.meta?.label ?? column.id) + .toLowerCase() + .includes(query.toLowerCase()), + ) + + const onDragEnd = (event: DragEndEvent) => { + const { active, over } = event + if (!over || active.id === over.id) return + + onColumnOrderChange((current) => { + const oldIndex = current.indexOf(String(active.id)) + const newIndex = current.indexOf(String(over.id)) + return oldIndex >= 0 && newIndex >= 0 + ? arrayMove(current, oldIndex, newIndex) + : current + }) + } + + return ( + + + + + + + setQuery(event.currentTarget.value)} + /> + + column.id)} + strategy={verticalListSortingStrategy} + > + + {columns.map((column) => ( + + + + column.toggleVisibility(!column.getIsVisible()) + } + /> + + + + ))} + + + + + + + ) +} + +function SortListPopover({ + table, + sorting, + onSortingChange, +}: { + table: AppTable + sorting: SortingState + onSortingChange: React.Dispatch> +}) { + const [opened, setOpened] = React.useState(false) + const sensors = useSensors( + useSensor(PointerSensor, { activationConstraint: { distance: 6 } }), + ) + const sortableColumns = table + .getAllColumns() + .filter((column) => column.getCanSort()) + const columnOptions = sortableColumns.map((column) => ({ + value: column.id, + label: column.columnDef.meta?.label ?? column.id, + })) + + const updateSort = (index: number, patch: Partial) => { + onSortingChange((current) => + current.map((sort, sortIndex) => + sortIndex === index ? { ...sort, ...patch } : sort, + ), + ) + } + + const addSort = () => { + const nextColumn = sortableColumns.find( + (column) => !sorting.some((sort) => sort.id === column.id), + ) + if (nextColumn) + onSortingChange((current) => [ + ...current, + { id: nextColumn.id, desc: false }, + ]) + } + + const onDragEnd = (event: DragEndEvent) => { + const { active, over } = event + if (!over || active.id === over.id) return + + onSortingChange((current) => { + const oldIndex = current.findIndex((sort) => sort.id === active.id) + const newIndex = current.findIndex((sort) => sort.id === over.id) + return oldIndex >= 0 && newIndex >= 0 + ? arrayMove(current, oldIndex, newIndex) + : current + }) + } + + return ( + + + + + + + + {sorting.length ? 'Sort by' : 'No sorting applied'} + + + sort.id)} + strategy={verticalListSortingStrategy} + > + + {sorting.map((sort, index) => ( + + + + + updateSort(index, { desc: value === 'desc' }) + } + w={110} + /> + + onSortingChange((current) => + current.filter( + (_, sortIndex) => sortIndex !== index, + ), + ) + } + > + + + + + ))} + + + + + + + + + + + ) +} + +function FilterValueInput({ + column, + filter, + onFilterUpdate, +}: { + column: AppColumn + filter: ExtendedColumnFilter + onFilterUpdate: ( + filterId: string, + patch: Partial, + ) => void +}) { + if (!filter.filterId) return null + const variant = column.columnDef.meta?.variant ?? 'text' + const operator = filter.operator ?? 'includesString' + const disabled = operator === 'isEmpty' || operator === 'isNotEmpty' + + if (disabled) { + return No value required + } + + if (variant === 'select') { + const options = column.columnDef.meta?.options ?? [] + return ( + { + if (!joinOperator) return + onColumnFiltersChange((current) => + current.map((item) => ({ ...item, joinOperator })), + ) + }} + w={90} + /> + ) : ( + + {filter.joinOperator ?? 'and'} + + )} + ({ + value: operator.value, + label: operator.label, + }))} + value={filter.operator ?? operators[0].value} + onChange={(operator) => { + if (!operator) return + updateFilter(filter.filterId!, { + operator, + value: '', + }) + }} + w={180} + /> + + + + + onColumnFiltersChange((current) => + current.filter( + (item) => item.filterId !== filter.filterId, + ), + ) + } + > + + + + ) + })} + + + + + + + + ) +} + +function Pagination({ table }: { table: AppTable }) { + const pageIndex = table.store.state.pagination.pageIndex + const pageSize = table.store.state.pagination.pageSize + + return ( + + + {table.getFilteredSelectedRowModel().rows.length.toLocaleString()} of{' '} + {table.getFilteredRowModel().rows.length.toLocaleString()} row(s) + selected. + + + Rows per page: + + updateSort(index, { + desc: event.target.value === 'desc', + }) + } + > + Asc + Desc + + + + onSortingChange((current) => + current.filter( + (_, sortIndex) => sortIndex !== index, + ), + ) + } + > + + + + + ))} + + + + + + + + + + + ) +} + +function FilterValueInput({ + column, + filter, + onFilterUpdate, +}: { + column: AppColumn + filter: ExtendedColumnFilter + onFilterUpdate: ( + filterId: string, + patch: Partial, + ) => void +}) { + if (!filter.filterId) return null + const variant = column.columnDef.meta?.variant ?? 'text' + const operator = filter.operator ?? 'includesString' + const disabled = operator === 'isEmpty' || operator === 'isNotEmpty' + + if (disabled) { + return No value required + } + + if (variant === 'select' || variant === 'multi-select') { + const options = column.columnDef.meta?.options ?? [] + const multiple = variant === 'multi-select' + const value = multiple + ? options.filter( + (option) => + Array.isArray(filter.value) && filter.value.includes(option.value), + ) + : (options.find((option) => option.value === filter.value) ?? null) + + return ( + option.label} + onChange={(_, nextValue) => { + onFilterUpdate(filter.filterId!, { + value: Array.isArray(nextValue) + ? nextValue.map((option) => option.value) + : nextValue?.value, + }) + }} + renderInput={(params) => } + /> + ) + } + + if (variant === 'date') { + if (operator === 'inRange') { + const value = Array.isArray(filter.value) ? filter.value : [] + return ( + + + onFilterUpdate(filter.filterId!, { + value: [ + event.target.value + ? new Date(event.target.value).toISOString() + : undefined, + value[1], + ], + }) + } + slotProps={{ inputLabel: { shrink: true } }} + /> + + onFilterUpdate(filter.filterId!, { + value: [ + value[0], + event.target.value + ? new Date(event.target.value).toISOString() + : undefined, + ], + }) + } + slotProps={{ inputLabel: { shrink: true } }} + /> + + ) + } + + return ( + + onFilterUpdate(filter.filterId!, { + value: event.target.value + ? new Date(event.target.value).toISOString() + : undefined, + }) + } + slotProps={{ inputLabel: { shrink: true } }} + /> + ) + } + + if (variant === 'number') { + return ( + + onFilterUpdate(filter.filterId!, { + value: event.target.value === '' ? '' : Number(event.target.value), + }) + } + /> + ) + } + + return ( + + onFilterUpdate(filter.filterId!, { value: event.target.value }) + } + /> + ) +} + +function FilterListPopover({ + table, + columnFilters, + onColumnFiltersChange, +}: { + table: AppTable + columnFilters: Array + onColumnFiltersChange: React.Dispatch< + React.SetStateAction> + > +}) { + const [anchorEl, setAnchorEl] = React.useState(null) + const filterableColumns = table + .getAllColumns() + .filter((column) => column.getCanFilter()) + + const updateFilter = ( + filterId: string, + patch: Partial, + ) => { + onColumnFiltersChange((current) => + current.map((filter) => + filter.filterId === filterId ? { ...filter, ...patch } : filter, + ), + ) + } + + const addFilter = () => { + if (filterableColumns.length === 0) return + const [column] = filterableColumns + onColumnFiltersChange((current) => [ + ...current, + { + id: column.id, + filterId: crypto.randomUUID(), + value: '', + operator: 'includesString', + joinOperator: current[0]?.joinOperator ?? 'and', + }, + ]) + } + + return ( + <> + + setAnchorEl(null)} + anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }} + > + + Filters + {columnFilters.map((filter, index) => { + const column = table.getColumn(filter.id) + if (!column || !filter.filterId) return null + const variant = column.columnDef.meta?.variant ?? 'text' + const operators = getFilterOperators(variant) + return ( + + {index === 0 ? ( + Where + ) : index === 1 ? ( + + + + ) : ( + + {filter.joinOperator ?? 'and'} + + )} + + option.columnDef.meta?.label ?? option.id + } + onChange={(_, nextColumn) => { + if (nextColumn) { + updateFilter(filter.filterId!, { + id: nextColumn.id, + operator: getFilterOperators( + nextColumn.columnDef.meta?.variant ?? 'text', + )[0].value, + value: '', + }) + } + }} + renderInput={(params) => ( + + )} + /> + + Operator + + + + + + + onColumnFiltersChange((current) => + current.filter( + (item) => item.filterId !== filter.filterId, + ), + ) + } + > + + + + ) + })} + + + + + + + + ) +} + +function Pagination({ table }: { table: AppTable }) { + return ( + + + {table.getFilteredSelectedRowModel().rows.length.toLocaleString()} of{' '} + {table.getFilteredRowModel().rows.length.toLocaleString()} row(s) + selected. + + + table.setPageIndex(page)} + onRowsPerPageChange={(event) => { + table.setPageSize(Number(event.target.value)) + table.setPageIndex(0) + }} + /> + + + ) +} + +function ModeMenu({ + mode, + setMode, +}: { + mode: 'light' | 'dark' | 'system' + setMode: React.Dispatch> +}) { + const [anchorEl, setAnchorEl] = React.useState(null) + + return ( + <> + + setAnchorEl(event.currentTarget)}> + {mode === 'dark' ? : } + + + setAnchorEl(null)} + > + {(['light', 'dark', 'system'] as const).map((themeMode) => ( + { + setMode(themeMode) + setAnchorEl(null) + }} + > + {toSentenceCase(themeMode)} + + ))} + + + ) +} + +function DebouncedTextField({ + value: initialValue, + onChange, + debounce = 300, + ...props +}: { + value: string | number + onChange: (value: string | number) => void + debounce?: number +} & Omit, 'onChange'>) { + const [value, setValue] = React.useState(initialValue) + + React.useEffect(() => { + setValue(initialValue) + }, [initialValue]) + + const debouncedOnChange = useDebouncedCallback(onChange, { wait: debounce }) + + return ( + { + setValue(event.target.value) + debouncedOnChange(event.target.value) + }} + /> + ) +} + +function App({ + mode, + setMode, +}: { + mode: 'light' | 'dark' | 'system' + setMode: React.Dispatch> +}) { + const rerender = React.useReducer(() => ({}), {})[1] + const [rowSelection, setRowSelection] = React.useState({}) + const [sorting, setSorting] = React.useState([]) + const [columnFilters, setColumnFilters] = React.useState< + Array + >([]) + const [columnVisibility, setColumnVisibility] = React.useState({}) + const [columnSizing, setColumnSizing] = React.useState({}) + const [globalFilter, setGlobalFilter] = React.useState('') + const [columnPinning, setColumnPinning] = React.useState({ + left: ['select'], + right: ['actions'], + }) + const [grouping, setGrouping] = React.useState([]) + const [expanded, setExpanded] = React.useState({}) + const [data, setData] = React.useState(() => makeData(1_000)) + + const columns = React.useMemo( + () => + columnHelper.columns([ + columnHelper.display({ + id: 'select', + header: ({ table }) => ( + + table.toggleAllPageRowsSelected(checked) + } + slotProps={{ input: { 'aria-label': 'Select all' } }} + size="small" + /> + ), + cell: ({ row }) => ( + row.toggleSelected(checked)} + slotProps={{ input: { 'aria-label': 'Select row' } }} + size="small" + /> + ), + maxSize: 48, + enableSorting: false, + enableHiding: false, + enableResizing: false, + }), + columnHelper.accessor('firstName', { + id: 'firstName', + header: ({ column }) => ( + + ), + cell: (info) => ( + {String(info.getValue())} + ), + meta: { label: 'First Name', variant: 'text' }, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + header: ({ column }) => ( + + ), + cell: (info) => ( + {String(info.getValue())} + ), + meta: { label: 'Last Name', variant: 'text' }, + }), + columnHelper.accessor('age', { + id: 'age', + header: ({ column }) => ( + + ), + cell: (info) => ( + {String(info.getValue())} + ), + aggregationFn: 'mean', + aggregatedCell: ({ getValue }) => ( + + Avg: {Math.round(Number(getValue()) * 10) / 10} + + ), + meta: { label: 'Age', variant: 'number' }, + }), + columnHelper.accessor('email', { + id: 'email', + header: ({ column }) => ( + + ), + cell: (info) => ( + {info.cell.getValue()} + ), + meta: { label: 'Email', variant: 'text' }, + }), + columnHelper.accessor('status', { + id: 'status', + header: ({ column }) => ( + + ), + cell: (info) => { + const status = info.getValue() + return status ? : null + }, + aggregatedCell: () => null, + meta: { + label: 'Status', + variant: 'select', + options: statuses.map((status) => ({ + label: toSentenceCase(status), + value: status, + })), + }, + }), + columnHelper.accessor('department', { + id: 'department', + header: ({ column }) => ( + + ), + cell: (info) => { + const department = info.getValue() + return department ? ( + + ) : null + }, + aggregatedCell: () => null, + meta: { + label: 'Department', + variant: 'multi-select', + options: departments.map((department) => ({ + label: toSentenceCase(department), + value: department, + })), + }, + }), + columnHelper.accessor('joinDate', { + id: 'joinDate', + header: ({ column }) => ( + + ), + cell: (info) => formatDate(info.getValue()), + aggregationFn: 'min', + aggregatedCell: ({ getValue }) => { + const earliest = getValue() + return ( + + Earliest: {earliest ? formatDate(earliest) : '—'} + + ) + }, + meta: { label: 'Join Date', variant: 'date' }, + }), + columnHelper.display({ + id: 'actions', + enableHiding: false, + cell: ({ row }) => , + maxSize: 44, + enableResizing: false, + }), + ]), + [], + ) + + const [columnOrder, setColumnOrder] = React.useState>(() => + columns.map((column) => column.id ?? ''), + ) + + const table = useTable( + { + _features, + _rowModels: { + coreRowModel: createCoreRowModel(), + filteredRowModel: createFilteredRowModel({ + ...filterFns, + fuzzy: fuzzyFilter, + }), + facetedRowModel: createFacetedRowModel(), + facetedUniqueValues: createFacetedUniqueValues(), + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + groupedRowModel: createGroupedRowModel(aggregationFns), + expandedRowModel: createExpandedRowModel(), + }, + columns, + data, + defaultColumn: { + minSize: 60, + maxSize: 800, + filterFn: dynamicFilterFn, + }, + globalFilterFn: 'fuzzy', + state: { + rowSelection, + sorting, + columnVisibility, + columnOrder, + columnSizing, + columnFilters, + globalFilter, + columnPinning, + grouping, + expanded, + }, + onSortingChange: setSorting, + onColumnVisibilityChange: setColumnVisibility, + onColumnOrderChange: setColumnOrder, + onColumnSizingChange: setColumnSizing, + onColumnFiltersChange: setColumnFilters, + onGlobalFilterChange: setGlobalFilter, + onColumnPinningChange: setColumnPinning, + onGroupingChange: setGrouping, + onExpandedChange: setExpanded, + getRowId: (row) => row.id, + enableRowSelection: true, + onRowSelectionChange: setRowSelection, + columnResizeMode: 'onChange', + debugTable: true, + }, + (state) => state, // default selector + ) + + const columnSizeVars = React.useMemo(() => { + const headers = table.getFlatHeaders() + const colSizes: Record = {} + for (const header of headers) { + colSizes[`--header-${header.id}-size`] = header.getSize() + colSizes[`--col-${header.column.id}-size`] = header.column.getSize() + } + return colSizes + }, [table.store.state.columnSizing]) + + const refreshData = () => setData(makeData(1_000)) + const stressTest = () => setData(makeData(200_000)) + + return ( + + + + + + + + + + + + + + + setGlobalFilter(String(value))} + placeholder="Search all columns..." + size="small" + sx={{ width: { xs: '100%', md: 360 } }} + slotProps={{ + input: { + startAdornment: ( + + + + ), + }, + }} + /> + + + + + + + + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers + .filter((header) => header.column.getIsVisible()) + .map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {cell.getIsGrouped() ? ( + + ) : cell.column.id === 'progress' ? ( + + + {String(cell.getValue())}% + + + + ) : ( + + )} + + ))} + + ))} + + + + + + + + + ) +} + +function ResizableHeaderCell({ + header, + table, +}: { + header: Header + table: { + FlexRender: React.ComponentType<{ + header: Header + }> + } +}) { + const sorting = React.useContext(SortingContext) + const sortDirection = getSortDirection(sorting, header.column.id) + + return ( + + + {header.isPlaceholder ? null : } + {header.column.getCanResize() ? ( + header.column.resetSize()} + onMouseDown={header.getResizeHandler()} + onTouchStart={header.getResizeHandler()} + sx={{ + position: 'absolute', + top: 0, + right: -6, + width: 6, + height: '100%', + cursor: 'col-resize', + touchAction: 'none', + bgcolor: header.column.getIsResizing() + ? 'primary.main' + : 'transparent', + '&:hover': { bgcolor: 'primary.main' }, + }} + /> + ) : null} + + + ) +} + +function Root() { + const prefersDark = useMediaQuery('(prefers-color-scheme: dark)') + const [mode, setMode] = React.useState<'light' | 'dark' | 'system'>('system') + const resolvedMode = + mode === 'system' ? (prefersDark ? 'dark' : 'light') : mode + const theme = React.useMemo( + () => + createTheme({ + palette: { + mode: resolvedMode, + }, + }), + [resolvedMode], + ) + + return ( + + + + + ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +ReactDOM.createRoot(rootElement).render( + + + , +) diff --git a/examples/react/kitchen-sink-material-ui/src/styles/globals.css b/examples/react/kitchen-sink-material-ui/src/styles/globals.css new file mode 100644 index 0000000000..3357669918 --- /dev/null +++ b/examples/react/kitchen-sink-material-ui/src/styles/globals.css @@ -0,0 +1,7 @@ +html { + color-scheme: light dark; +} + +body { + margin: 0; +} diff --git a/examples/react/kitchen-sink-material-ui/src/types/index.ts b/examples/react/kitchen-sink-material-ui/src/types/index.ts new file mode 100644 index 0000000000..4958d1736a --- /dev/null +++ b/examples/react/kitchen-sink-material-ui/src/types/index.ts @@ -0,0 +1,38 @@ +import type { + ColumnFilter, + TableFeatures, + filterFns, +} from '@tanstack/react-table' + +export type TableFilterFeatures = Pick< + TFeatures, + 'columnFilteringFeature' | 'columnFacetingFeature' +> + +export type FilterOperator = + | keyof typeof filterFns + | 'notIncludesString' + | 'notEqualsString' + | 'notEquals' + | 'greaterThan' + | 'notGreaterThan' + | 'greaterThanOrEqualTo' + | 'notGreaterThanOrEqualTo' + | 'lessThan' + | 'notLessThan' + | 'lessThanOrEqualTo' + | 'notLessThanOrEqualTo' + | 'isRelativeToToday' + | 'inRange' + | 'startsWith' + | 'endsWith' + | 'isEmpty' + | 'isNotEmpty' + +export type JoinOperator = 'and' | 'or' + +export interface ExtendedColumnFilter extends ColumnFilter { + filterId?: string + operator?: FilterOperator + joinOperator?: JoinOperator +} diff --git a/examples/react/kitchen-sink-material-ui/src/vite-env.d.ts b/examples/react/kitchen-sink-material-ui/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/kitchen-sink-material-ui/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/kitchen-sink-material-ui/tsconfig.json b/examples/react/kitchen-sink-material-ui/tsconfig.json new file mode 100644 index 0000000000..6714dcfb68 --- /dev/null +++ b/examples/react/kitchen-sink-material-ui/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "paths": { + "@/*": ["./src/*"] + }, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/react/kitchen-sink-material-ui/vite.config.js b/examples/react/kitchen-sink-material-ui/vite.config.js new file mode 100644 index 0000000000..5a7f5e1a28 --- /dev/null +++ b/examples/react/kitchen-sink-material-ui/vite.config.js @@ -0,0 +1,29 @@ +import path from 'node:path' +import { defineConfig } from 'vite' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), + ], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, +}) diff --git a/examples/react/kitchen-sink-react-aria/.gitignore b/examples/react/kitchen-sink-react-aria/.gitignore new file mode 100644 index 0000000000..18a5070e2a --- /dev/null +++ b/examples/react/kitchen-sink-react-aria/.gitignore @@ -0,0 +1,6 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local +!lib diff --git a/examples/react/kitchen-sink-react-aria/README.md b/examples/react/kitchen-sink-react-aria/README.md new file mode 100644 index 0000000000..b79780e0b0 --- /dev/null +++ b/examples/react/kitchen-sink-react-aria/README.md @@ -0,0 +1,6 @@ +# React Kitchen Sink React Aria Example + +To run this example: + +- `pnpm install` +- `pnpm start` diff --git a/examples/react/kitchen-sink-react-aria/index.html b/examples/react/kitchen-sink-react-aria/index.html new file mode 100644 index 0000000000..1551290f72 --- /dev/null +++ b/examples/react/kitchen-sink-react-aria/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/react/kitchen-sink-react-aria/package.json b/examples/react/kitchen-sink-react-aria/package.json new file mode 100644 index 0000000000..d11dffdcf2 --- /dev/null +++ b/examples/react/kitchen-sink-react-aria/package.json @@ -0,0 +1,39 @@ +{ + "name": "tanstack-react-table-example-kitchen-sink-react-aria", + "type": "module", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", + "@faker-js/faker": "^10.4.0", + "@tailwindcss/vite": "^4.3.0", + "@tanstack/match-sorter-utils": "^9.0.0-alpha.4", + "@tanstack/react-pacer": "^0.22.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "date-fns": "^4.1.0", + "react": "^19.2.6", + "react-aria-components": "^1.17.0", + "react-dom": "^19.2.6", + "tailwindcss": "^4.3.0" + }, + "devDependencies": { + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/react/kitchen-sink-react-aria/src/lib/data-table.ts b/examples/react/kitchen-sink-react-aria/src/lib/data-table.ts new file mode 100644 index 0000000000..24568c1a9d --- /dev/null +++ b/examples/react/kitchen-sink-react-aria/src/lib/data-table.ts @@ -0,0 +1,502 @@ +import { + filterFn_arrIncludes, + filterFn_equals, + filterFn_equalsString, + filterFn_greaterThan, + filterFn_greaterThanOrEqualTo, + filterFn_includesString, + filterFn_lessThan, + filterFn_lessThanOrEqualTo, +} from '@tanstack/react-table' +import { rankItem } from '@tanstack/match-sorter-utils' +import type { + ExtendedColumnFilter, + FilterOperator, + JoinOperator, + TableFilterFeatures, +} from '@/types' +import type { RankingInfo } from '@tanstack/match-sorter-utils' +import type { + FilterFn, + Row, + RowData, + TableFeatures, +} from '@tanstack/react-table' + +/** + * Fuzzy filter using @tanstack/match-sorter-utils. Used as the global filter + * (`globalFilterFn: 'fuzzy'`) in the kitchen-sink example so the toolbar + * search ranks rows by best match across all columns. + * + * Mirrors the canonical pattern from `examples/react/filters-fuzzy`. Written + * as a plain function (not a typed const) so it stays generic over TFeatures + * and TData and can be slotted into any `createFilteredRowModel({...})` + * registration without narrowing inference. + */ +export function fuzzyFilter< + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + value: string, + addMeta?: (meta: { itemRank: RankingInfo }) => void, +): boolean { + // Rank the item + const itemRank = rankItem(row.getValue(columnId), value) + + // Store the itemRank info so a fuzzy sort function (if any) could reuse it + addMeta?.({ itemRank }) + + // Return whether the item should be filtered in/out + return itemRank.passed +} + +declare module '@tanstack/react-table' { + interface FilterFns { + fuzzy: FilterFn + } + interface FilterMeta { + itemRank?: RankingInfo + } +} + +function isFalsy(val: unknown) { + return ( + val === undefined || + val === null || + val === '' || + (Array.isArray(val) && val.length === 0) + ) +} + +function isValidDate(value: unknown): boolean { + if (value instanceof Date) return !isNaN(value.getTime()) + if (typeof value === 'string') return !isNaN(Date.parse(value)) + return false +} + +function toDate(value: unknown): Date | null { + if (value instanceof Date) return value + if (typeof value === 'string') { + const date = new Date(value) + return !isNaN(date.getTime()) ? date : null + } + return null +} + +function isSameDay(date1: Date, date2: Date): boolean { + const date1Str = date1.toISOString().split('T')[0] + const date2Str = date2.toISOString().split('T')[0] + return date1Str === date2Str +} + +const filterFn_enhancedEquals: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + filterValue: unknown, +) => { + const rowValue = row.getValue(columnId) + + if (Array.isArray(filterValue)) { + return filterFn_arrIncludes(row, columnId, filterValue) + } + + if (isValidDate(rowValue) && isValidDate(filterValue)) { + const rowDate = toDate(rowValue) + const filterDate = toDate(filterValue) + + if (rowDate && filterDate) { + return isSameDay(rowDate, filterDate) + } + } + + return filterFn_equals(row, columnId, filterValue) +} + +filterFn_enhancedEquals.resolveFilterValue = (val: any) => isFalsy(val) + +const filterFn_enhancedGreaterThan: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + filterValue: unknown, +) => { + const rowValue = row.getValue(columnId) + + if (isValidDate(rowValue) && isValidDate(filterValue)) { + const rowDate = toDate(rowValue) + const filterDate = toDate(filterValue) + + if (rowDate && filterDate) { + return rowDate.getTime() > filterDate.getTime() + } + } + + return filterFn_greaterThan(row, columnId, filterValue) +} + +filterFn_enhancedGreaterThan.resolveFilterValue = (val: any) => isFalsy(val) + +const filterFn_enhancedGreaterThanOrEqualTo: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + filterValue: unknown, +) => { + const rowValue = row.getValue(columnId) + + if (isValidDate(rowValue) && isValidDate(filterValue)) { + const rowDate = toDate(rowValue) + const filterDate = toDate(filterValue) + + if (rowDate && filterDate) { + return rowDate.getTime() >= filterDate.getTime() + } + } + + return filterFn_greaterThanOrEqualTo(row, columnId, filterValue) +} + +filterFn_enhancedGreaterThanOrEqualTo.resolveFilterValue = (val: any) => + isFalsy(val) + +const filterFn_enhancedLessThan: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + filterValue: unknown, +) => { + const rowValue = row.getValue(columnId) + + if (isValidDate(rowValue) && isValidDate(filterValue)) { + const rowDate = toDate(rowValue) + const filterDate = toDate(filterValue) + + if (rowDate && filterDate) { + return rowDate.getTime() < filterDate.getTime() + } + } + + return filterFn_lessThan(row, columnId, filterValue) +} + +filterFn_enhancedLessThan.resolveFilterValue = (val: any) => isFalsy(val) + +const filterFn_enhancedLessThanOrEqualTo: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + filterValue: unknown, +) => { + const rowValue = row.getValue(columnId) + + if (isValidDate(rowValue) && isValidDate(filterValue)) { + const rowDate = toDate(rowValue) + const filterDate = toDate(filterValue) + + if (rowDate && filterDate) { + return rowDate.getTime() <= filterDate.getTime() + } + } + + return filterFn_lessThanOrEqualTo(row, columnId, filterValue) +} + +filterFn_enhancedLessThanOrEqualTo.resolveFilterValue = (val: any) => + isFalsy(val) + +const filterFn_startsWith: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + filterValue: string, +) => { + const value = String(row.getValue(columnId) ?? '').toLowerCase() + return value.startsWith(filterValue.toLowerCase()) +} + +filterFn_startsWith.resolveFilterValue = (val: any) => isFalsy(val) + +const filterFn_endsWith: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + filterValue: string, +) => { + const value = String(row.getValue(columnId) ?? '').toLowerCase() + return value.endsWith(filterValue.toLowerCase()) +} + +filterFn_endsWith.resolveFilterValue = (val: any) => isFalsy(val) + +const filterFn_isEmpty: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, +) => { + const value = row.getValue(columnId) + return ( + value === undefined || + value === null || + value === '' || + (Array.isArray(value) && value.length === 0) + ) +} + +filterFn_isEmpty.resolveFilterValue = (val: any) => isFalsy(val) + +const filterFn_inBetween: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + filterValue: unknown, +) => { + if (Array.isArray(filterValue)) { + const [min, max] = filterValue + const rowValue: unknown = row.getValue(columnId) + + if (min === undefined || min === '' || min === null) { + return max === undefined || max === '' || max === null + ? true + : typeof rowValue === 'number' && typeof max === 'number' + ? rowValue <= max + : String(rowValue) <= String(max) + } + if (max === undefined || max === '' || max === null) { + return typeof rowValue === 'number' && typeof min === 'number' + ? rowValue >= min + : String(rowValue) >= String(min) + } + + if ( + rowValue instanceof Date || + (typeof rowValue === 'string' && !isNaN(Date.parse(rowValue))) + ) { + const dateValue = new Date(rowValue).getTime() + const minDate = new Date(min as string | Date).getTime() + const maxDate = new Date(max as string | Date).getTime() + return dateValue >= minDate && dateValue <= maxDate + } + + const numValue = Number(rowValue) + return ( + !isNaN(numValue) && numValue >= Number(min) && numValue <= Number(max) + ) + } + return true +} + +filterFn_inBetween.autoRemove = (val: any) => isFalsy(val) + +const filterFn_isRelativeToToday: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, + columnId: string, + filterValue: unknown, +) => { + const rowValue = row.getValue(columnId) + + if (!isValidDate(rowValue)) return false + + const rowDate = toDate(rowValue) + if (!rowDate) return false + + rowDate.setHours(0, 0, 0, 0) + + const today = new Date() + today.setHours(0, 0, 0, 0) + + const diffInDays = Math.floor( + (rowDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24), + ) + + if (typeof filterValue === 'number') { + return diffInDays === filterValue + } else if (typeof filterValue === 'string') { + const numValue = parseInt(filterValue, 10) + if (!isNaN(numValue)) { + return diffInDays === numValue + } + } else if (Array.isArray(filterValue) && filterValue.length === 2) { + const [min, max] = filterValue + const minDays = typeof min === 'number' ? min : parseInt(min as string, 10) + const maxDays = typeof max === 'number' ? max : parseInt(max as string, 10) + + if (!isNaN(minDays) && !isNaN(maxDays)) { + return diffInDays >= minDays && diffInDays <= maxDays + } + } + + return false +} + +filterFn_isRelativeToToday.autoRemove = (val: any) => isFalsy(val) + +export const dynamicFilterFn: FilterFn = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + row: Row, TData>, + columnId: string, + filterValue: unknown, +) => { + let operator: FilterOperator = 'includesString' + let value = filterValue + let joinOperator: JoinOperator = 'and' + + const filters: Array = + row.table.store.state.columnFilters.filter((f) => f.id === columnId) + + if (!filters.length) return true + + if (filters[0].joinOperator) { + joinOperator = filters[0].joinOperator + } + + return filters.reduce((pass, filter, index) => { + operator = filter.operator ?? 'includesString' + value = filter.value + + if (isFalsy(value) && operator !== 'isEmpty' && operator !== 'isNotEmpty') { + return pass + } + + let result: boolean + switch (operator) { + case 'includesString': + result = filterFn_includesString(row, columnId, value) + break + case 'notIncludesString': + result = !filterFn_includesString(row, columnId, value) + break + case 'equalsString': + result = filterFn_equalsString(row, columnId, value) + break + case 'notEqualsString': + result = !filterFn_equalsString(row, columnId, value) + break + case 'startsWith': + result = filterFn_startsWith(row, columnId, value) + break + case 'endsWith': + result = filterFn_endsWith(row, columnId, value) + break + case 'isEmpty': + result = filterFn_isEmpty(row, columnId, '') + break + case 'isNotEmpty': + result = !filterFn_isEmpty(row, columnId, '') + break + case 'equals': + result = filterFn_enhancedEquals(row, columnId, value) + break + case 'notEquals': + result = !filterFn_enhancedEquals(row, columnId, value) + break + case 'greaterThan': + result = filterFn_enhancedGreaterThan(row, columnId, value) + break + case 'greaterThanOrEqualTo': + result = filterFn_enhancedGreaterThanOrEqualTo(row, columnId, value) + break + case 'lessThan': + result = filterFn_enhancedLessThan(row, columnId, value) + break + case 'lessThanOrEqualTo': + result = filterFn_enhancedLessThanOrEqualTo(row, columnId, value) + break + case 'inRange': + result = filterFn_inBetween(row, columnId, value) + break + case 'isRelativeToToday': + result = filterFn_isRelativeToToday(row, columnId, value) + break + default: + result = filterFn_includesString(row, columnId, value) + break + } + + if (index === 0) return result + + return joinOperator === 'and' ? pass && result : pass || result + }, true) +} + +export function getFilterOperators(type: string): Array<{ + label: string + value: FilterOperator +}> { + switch (type) { + case 'text': + return [ + { label: 'contains', value: 'includesString' }, + { label: 'does not contain', value: 'notIncludesString' }, + { label: 'starts with', value: 'startsWith' }, + { label: 'ends with', value: 'endsWith' }, + { label: 'is', value: 'equalsString' }, + { label: 'is not', value: 'notEqualsString' }, + { label: 'is empty', value: 'isEmpty' }, + { label: 'is not empty', value: 'isNotEmpty' }, + ] + case 'number': + return [ + { label: 'is', value: 'equals' }, + { label: 'is not', value: 'notEquals' }, + { label: 'is less than', value: 'lessThan' }, + { label: 'is less than or equal to', value: 'lessThanOrEqualTo' }, + { label: 'is greater than', value: 'greaterThan' }, + { label: 'is greater than or equal to', value: 'greaterThanOrEqualTo' }, + { label: 'is between', value: 'inRange' }, + ] + case 'date': + return [ + { label: 'is', value: 'equals' }, + { label: 'is not', value: 'notEquals' }, + { label: 'is before', value: 'lessThan' }, + { label: 'is on or before', value: 'lessThanOrEqualTo' }, + { label: 'is after', value: 'greaterThan' }, + { label: 'is on or after', value: 'greaterThanOrEqualTo' }, + { label: 'is between', value: 'inRange' }, + { label: 'is relative to today', value: 'isRelativeToToday' }, + { label: 'is empty', value: 'isEmpty' }, + { label: 'is not empty', value: 'isNotEmpty' }, + ] + case 'select': + case 'multi-select': + return [ + { label: 'is', value: 'equals' }, + { label: 'is not', value: 'notEquals' }, + { label: 'is empty', value: 'isEmpty' }, + { label: 'is not empty', value: 'isNotEmpty' }, + ] + default: + return [ + { label: 'contains', value: 'includesString' }, + { label: 'does not contain', value: 'notIncludesString' }, + { label: 'is', value: 'equalsString' }, + { label: 'is not', value: 'notEqualsString' }, + ] + } +} diff --git a/examples/react/kitchen-sink-react-aria/src/lib/make-data.ts b/examples/react/kitchen-sink-react-aria/src/lib/make-data.ts new file mode 100644 index 0000000000..8198ee2581 --- /dev/null +++ b/examples/react/kitchen-sink-react-aria/src/lib/make-data.ts @@ -0,0 +1,57 @@ +import { faker } from '@faker-js/faker' + +export const statuses = ['active', 'inactive', 'pending'] as const +export const departments = [ + 'engineering', + 'marketing', + 'finance', + 'sales', + 'hr', +] as const + +export interface Person { + id: string + firstName: string + lastName: string + age: number + email: string + status: (typeof statuses)[number] + department: (typeof departments)[number] + joinDate: Date + subRows?: Array +} + +function range(len: number) { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +function newPerson(): Person { + return { + id: faker.string.uuid(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int({ min: 20, max: 65 }), + email: faker.internet.email(), + status: faker.helpers.arrayElement(statuses), + department: faker.helpers.arrayElement(departments), + joinDate: faker.date.past({ years: 5 }), + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/react/kitchen-sink-react-aria/src/main.tsx b/examples/react/kitchen-sink-react-aria/src/main.tsx new file mode 100644 index 0000000000..d3ef6dd0d4 --- /dev/null +++ b/examples/react/kitchen-sink-react-aria/src/main.tsx @@ -0,0 +1,1571 @@ +'use client' + +import * as React from 'react' +import * as ReactDOM from 'react-dom/client' +import { useDebouncedCallback } from '@tanstack/react-pacer/debouncer' +import { + DndContext, + PointerSensor, + closestCenter, + useSensor, + useSensors, +} from '@dnd-kit/core' +import { + SortableContext, + arrayMove, + useSortable, + verticalListSortingStrategy, +} from '@dnd-kit/sortable' +import { CSS } from '@dnd-kit/utilities' +import { + Column as AriaColumn, + Table as AriaTable, + Button, + Cell, + Checkbox, + Dialog, + DialogTrigger, + Input, + Label, + ListBox, + ListBoxItem, + Menu, + MenuItem, + MenuTrigger, + Popover, + ProgressBar, + Row, + Select, + SelectValue, + Switch, + TableBody, + TableHeader, + Tooltip, + TooltipTrigger, +} from 'react-aria-components' +import { + aggregationFns, + columnFacetingFeature, + columnFilteringFeature, + columnGroupingFeature, + columnOrderingFeature, + columnPinningFeature, + columnResizingFeature, + columnSizingFeature, + columnVisibilityFeature, + createColumnHelper, + createCoreRowModel, + createExpandedRowModel, + createFacetedRowModel, + createFacetedUniqueValues, + createFilteredRowModel, + createGroupedRowModel, + createPaginatedRowModel, + createSortedRowModel, + filterFns, + globalFilteringFeature, + rowExpandingFeature, + rowPaginationFeature, + rowSelectionFeature, + rowSortingFeature, + sortFns, + tableFeatures, + useTable, +} from '@tanstack/react-table' +import type { DragEndEvent } from '@dnd-kit/core' +import type { Key } from 'react-aria-components' +import type { Person } from '@/lib/make-data' +import type { + CellData, + Column, + ColumnPinningState, + ColumnSizingState, + ExpandedState, + GroupingState, + Header, + RowData, + RowSelectionState, + SortingState, + Table, + TableFeatures, +} from '@tanstack/react-table' +import type { ExtendedColumnFilter } from '@/types' + +import { + dynamicFilterFn, + fuzzyFilter, + getFilterOperators, +} from '@/lib/data-table' +import { departments, makeData, statuses } from '@/lib/make-data' +import './styles/globals.css' + +declare module '@tanstack/react-table' { + interface ColumnMeta< + TFeatures extends TableFeatures, + TData extends RowData, + TValue extends CellData = CellData, + > { + label?: string + variant?: 'text' | 'number' | 'date' | 'boolean' | 'select' | 'multi-select' + options?: Array<{ label: string; value: string; count?: number }> + } +} + +const _features = tableFeatures({ + rowSortingFeature, + rowPaginationFeature, + rowSelectionFeature, + rowExpandingFeature, + columnFilteringFeature, + columnFacetingFeature, + columnOrderingFeature, + columnVisibilityFeature, + columnSizingFeature, + columnResizingFeature, + columnPinningFeature, + columnGroupingFeature, + globalFilteringFeature, +}) + +const columnHelper = createColumnHelper() +type AppTable = Table +type AppColumn = Column + +function cx(...classes: Array) { + return classes.filter(Boolean).join(' ') +} + +function getPageItems(pageIndex: number, pageCount: number) { + const currentPage = pageIndex + 1 + const pages = new Set([ + 1, + pageCount, + currentPage - 1, + currentPage, + currentPage + 1, + ]) + + return Array.from(pages) + .filter((page) => page >= 1 && page <= pageCount) + .sort((a, b) => a - b) + .reduce>((items, page) => { + const previous = items[items.length - 1] + if (typeof previous === 'number' && page - previous > 1) { + items.push('ellipsis') + } + items.push(page) + return items + }, []) +} + +function SortableFrame({ + id, + children, +}: { + id: string + children: React.ReactNode +}) { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id }) + + return ( +
+ {children} +
+ ) +} + +function toSentenceCase(value: string) { + return value + .replace(/[-_]/g, ' ') + .replace(/\w\S*/g, (word) => word[0].toUpperCase() + word.slice(1)) +} + +function formatDate(value: unknown) { + return new Intl.DateTimeFormat('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + }).format(new Date(String(value))) +} + +function toDateInputValue(value: unknown) { + if (!value) return '' + const date = new Date(String(value)) + return Number.isNaN(date.getTime()) ? '' : date.toISOString().slice(0, 10) +} + +function getAriaSort(sortDirection: false | 'asc' | 'desc') { + if (sortDirection === 'asc') return 'ascending' + if (sortDirection === 'desc') return 'descending' + return 'none' +} + +const SortingContext = React.createContext([]) + +function getSortDirection(sorting: SortingState, columnId: string) { + const sort = sorting.find((item) => item.id === columnId) + return sort ? (sort.desc ? 'desc' : 'asc') : undefined +} + +function getCommonPinningStyles( + column: AppColumn, + isSelected = false, +): React.CSSProperties { + const isPinned = column.getIsPinned() + const isLastLeftPinnedColumn = + isPinned === 'left' && column.getIsLastColumn('left') + const isFirstRightPinnedColumn = + isPinned === 'right' && column.getIsFirstColumn('right') + + return { + boxShadow: isLastLeftPinnedColumn + ? '-4px 0 4px -4px var(--border) inset' + : isFirstRightPinnedColumn + ? '4px 0 4px -4px var(--border) inset' + : undefined, + left: isPinned === 'left' ? `${column.getStart('left')}px` : undefined, + right: isPinned === 'right' ? `${column.getAfter('right')}px` : undefined, + position: isPinned ? 'sticky' : 'relative', + borderRight: isLastLeftPinnedColumn ? '1px solid var(--border)' : undefined, + borderLeft: isFirstRightPinnedColumn + ? '1px solid var(--border)' + : undefined, + background: isSelected + ? 'color-mix(in srgb, var(--primary) 12%, var(--surface))' + : isPinned + ? 'var(--surface)' + : undefined, + zIndex: isPinned ? 2 : 0, + } +} + +function DepartmentPill({ department }: { department: Person['department'] }) { + return ( + + + {department.slice(0, 2).toUpperCase()} + + {toSentenceCase(department)} + + ) +} + +function EllipsisText({ children }: { children: React.ReactNode }) { + return {children} +} + +function StatusBadge({ status }: { status: Person['status'] }) { + const className: Record = { + active: 'status-badge status-active', + inactive: 'status-badge status-inactive', + pending: 'status-badge status-pending', + } + + return {toSentenceCase(status)} +} + +function SelectionCheckbox({ + ariaLabel, + isSelected, + isIndeterminate, + onChange, +}: { + ariaLabel: string + isSelected: boolean + isIndeterminate?: boolean + onChange: (selected: boolean) => void +}) { + const ref = React.useRef(null) + + React.useEffect(() => { + if (ref.current) ref.current.indeterminate = !!isIndeterminate + }, [isIndeterminate]) + + return ( + onChange(event.currentTarget.checked)} + /> + ) +} + +function RowActions({ person }: { person: Person }) { + return ( + + + + + { + void navigator.clipboard.writeText(person.id) + }} + > + Copy ID + + View details + View profile + + + + ) +} + +function SortIcon({ direction }: { direction: 'asc' | 'desc' | undefined }) { + if (direction === 'asc') return + if (direction === 'desc') return + return ( + + ) +} + +function ColumnHeaderMenu({ + column, + title, +}: { + column: AppColumn + title: string +}) { + const canSort = column.getCanSort() + const canHide = column.getCanHide() + const canPin = column.getCanPin() + const canGroup = column.getCanGroup() + const sorting = React.useContext(SortingContext) + const direction = canSort ? getSortDirection(sorting, column.id) : undefined + const pinned = canPin ? column.getIsPinned() : false + const grouped = canGroup ? column.getIsGrouped() : false + + if (!canSort && !canHide && !canPin && !canGroup) { + return {title} + } + + return ( +
+ {canSort ? ( + + ) : ( + {title} + )} + + + + + {canSort ? ( + <> + column.toggleSorting(false)}> + Asc + + column.toggleSorting(true)}> + Desc + + + ) : null} + {canGroup ? ( + + {grouped ? 'Ungroup' : 'Group by'} + + ) : null} + {canPin ? ( + <> + column.pin('left')} + > + Pin left + + column.pin('right')} + > + Pin right + + {pinned ? ( + column.pin(false)}> + Unpin + + ) : null} + + ) : null} + {canHide ? ( + column.toggleVisibility(false)} + > + Hide + + ) : null} + + + +
+ ) +} + +function AriaSelect({ + label, + value, + options, + className, + showLabel = true, + onChange, +}: { + label: string + value: string | null + options: Array<{ value: string; label: string }> + className?: string + showLabel?: boolean + onChange: (value: string) => void +}) { + return ( + + ) +} + +function ViewOptionsPopover({ + table, + columnOrder, + onColumnOrderChange, +}: { + table: AppTable + columnOrder: Array + onColumnOrderChange: React.Dispatch>> +}) { + const [query, setQuery] = React.useState('') + const sensors = useSensors( + useSensor(PointerSensor, { activationConstraint: { distance: 6 } }), + ) + const columns = table + .getAllColumns() + .filter((column) => typeof column.accessorFn !== 'undefined') + .sort((a, b) => columnOrder.indexOf(a.id) - columnOrder.indexOf(b.id)) + .filter((column) => + (column.columnDef.meta?.label ?? column.id) + .toLowerCase() + .includes(query.toLowerCase()), + ) + + const onDragEnd = (event: DragEndEvent) => { + const { active, over } = event + if (!over || active.id === over.id) return + + onColumnOrderChange((current) => { + const oldIndex = current.indexOf(String(active.id)) + const newIndex = current.indexOf(String(over.id)) + return oldIndex >= 0 && newIndex >= 0 + ? arrayMove(current, oldIndex, newIndex) + : current + }) + } + + return ( + + + + + setQuery(event.currentTarget.value)} + /> + + column.id)} + strategy={verticalListSortingStrategy} + > +
+ {columns.map((column) => ( + +
+ + column.toggleVisibility(selected) + } + > + {column.columnDef.meta?.label ?? column.id} + + +
+
+ ))} +
+
+
+
+
+
+ ) +} + +function SortListPopover({ + table, + sorting, + onSortingChange, +}: { + table: AppTable + sorting: SortingState + onSortingChange: React.Dispatch> +}) { + const sensors = useSensors( + useSensor(PointerSensor, { activationConstraint: { distance: 6 } }), + ) + const sortableColumns = table + .getAllColumns() + .filter((column) => column.getCanSort()) + const columnOptions = sortableColumns.map((column) => ({ + value: column.id, + label: column.columnDef.meta?.label ?? column.id, + })) + + const updateSort = (index: number, patch: Partial) => { + onSortingChange((current) => + current.map((sort, sortIndex) => + sortIndex === index ? { ...sort, ...patch } : sort, + ), + ) + } + + const addSort = () => { + const nextColumn = sortableColumns.find( + (column) => !sorting.some((sort) => sort.id === column.id), + ) + if (nextColumn) { + onSortingChange((current) => [ + ...current, + { id: nextColumn.id, desc: false }, + ]) + } + } + + const onDragEnd = (event: DragEndEvent) => { + const { active, over } = event + if (!over || active.id === over.id) return + + onSortingChange((current) => { + const oldIndex = current.findIndex((sort) => sort.id === active.id) + const newIndex = current.findIndex((sort) => sort.id === over.id) + return oldIndex >= 0 && newIndex >= 0 + ? arrayMove(current, oldIndex, newIndex) + : current + }) + } + + return ( + + + + +
+ {sorting.length ? 'Sort by' : 'No sorting applied'} +
+ + sort.id)} + strategy={verticalListSortingStrategy} + > +
+ {sorting.map((sort, index) => ( + +
+ + updateSort(index, { id: value })} + /> + + updateSort(index, { desc: value === 'desc' }) + } + /> + +
+
+ ))} +
+
+
+
+ + +
+
+
+
+ ) +} + +function FilterValueInput({ + column, + filter, + onFilterUpdate, +}: { + column: AppColumn + filter: ExtendedColumnFilter + onFilterUpdate: ( + filterId: string, + patch: Partial, + ) => void +}) { + if (!filter.filterId) return null + const variant = column.columnDef.meta?.variant ?? 'text' + const operator = filter.operator ?? 'includesString' + const disabled = operator === 'isEmpty' || operator === 'isNotEmpty' + + if (disabled) + return
No value required
+ + if (variant === 'select') { + const options = column.columnDef.meta?.options ?? [] + return ( + onFilterUpdate(filter.filterId!, { value })} + /> + ) + } + + if (variant === 'multi-select') { + const options = column.columnDef.meta?.options ?? [] + const values = Array.isArray(filter.value) + ? filter.value.map(String) + : typeof filter.value === 'string' && filter.value + ? [filter.value] + : [] + return ( + + ) + } + + if (variant === 'date') { + if (operator === 'inRange') { + const value = Array.isArray(filter.value) ? filter.value : [] + return ( +
+ + onFilterUpdate(filter.filterId!, { + value: [ + event.currentTarget.value + ? new Date(event.currentTarget.value).toISOString() + : undefined, + value[1], + ], + }) + } + /> + + onFilterUpdate(filter.filterId!, { + value: [ + value[0], + event.currentTarget.value + ? new Date(event.currentTarget.value).toISOString() + : undefined, + ], + }) + } + /> +
+ ) + } + + return ( + + onFilterUpdate(filter.filterId!, { + value: event.currentTarget.value + ? new Date(event.currentTarget.value).toISOString() + : undefined, + }) + } + /> + ) + } + + if (variant === 'number') { + return ( + + onFilterUpdate(filter.filterId!, { + value: + event.currentTarget.value === '' + ? '' + : Number(event.currentTarget.value), + }) + } + /> + ) + } + + return ( + + onFilterUpdate(filter.filterId!, { value: event.currentTarget.value }) + } + /> + ) +} + +function FilterListPopover({ + table, + columnFilters, + onColumnFiltersChange, +}: { + table: AppTable + columnFilters: Array + onColumnFiltersChange: React.Dispatch< + React.SetStateAction> + > +}) { + const filterableColumns = table + .getAllColumns() + .filter((column) => column.getCanFilter()) + const fieldOptions = filterableColumns.map((column) => ({ + value: column.id, + label: column.columnDef.meta?.label ?? column.id, + })) + + const updateFilter = ( + filterId: string, + patch: Partial, + ) => { + onColumnFiltersChange((current) => + current.map((filter) => + filter.filterId === filterId ? { ...filter, ...patch } : filter, + ), + ) + } + + const addFilter = () => { + const [column] = filterableColumns + if (!column) return + onColumnFiltersChange((current) => [ + ...current, + { + id: column.id, + filterId: crypto.randomUUID(), + value: '', + operator: 'includesString', + joinOperator: current[0]?.joinOperator ?? 'and', + }, + ]) + } + + return ( + + + + +
Filters
+ {columnFilters.map((filter, index) => { + const column = table.getColumn(filter.id) + if (!column || !filter.filterId) return null + const variant = column.columnDef.meta?.variant ?? 'text' + const operators = getFilterOperators(variant) + return ( +
+ {index === 0 ? ( +
Where
+ ) : index === 1 ? ( + + onColumnFiltersChange((current) => + current.map((item) => ({ + ...item, + joinOperator: joinOperator as 'and' | 'or', + })), + ) + } + /> + ) : ( +
+ {filter.joinOperator ?? 'and'} +
+ )} + { + const nextColumn = table.getColumn(nextColumnId) + if (nextColumn) { + updateFilter(filter.filterId!, { + id: nextColumn.id, + operator: getFilterOperators( + nextColumn.columnDef.meta?.variant ?? 'text', + )[0].value, + value: '', + }) + } + }} + /> + ({ + value: operator.value, + label: operator.label, + }))} + onChange={(operator) => + updateFilter(filter.filterId!, { + operator: operator as ExtendedColumnFilter['operator'], + value: '', + }) + } + /> + + +
+ ) + })} +
+ + +
+
+
+
+ ) +} + +function Pagination({ table }: { table: AppTable }) { + const pageIndex = table.store.state.pagination.pageIndex + const pageSize = table.store.state.pagination.pageSize + const pageItems = getPageItems(pageIndex, table.getPageCount()) + + return ( +
+
+ {table.getFilteredSelectedRowModel().rows.length.toLocaleString()} of{' '} + {table.getFilteredRowModel().rows.length.toLocaleString()} row(s) + selected. +
+
+ Rows per page: + ({ + value, + label: value, + }))} + onChange={(value) => { + table.setPageSize(Number(value)) + table.setPageIndex(0) + }} + /> + + + +
+
+ ) +} + +function ModeSwitch() { + const [isDark, setIsDark] = React.useState(() => + document.documentElement.classList.contains('dark'), + ) + + React.useEffect(() => { + document.documentElement.classList.toggle('dark', isDark) + }, [isDark]) + + return ( + + + + + + + Theme + + ) +} + +function DebouncedTextInput({ + value: initialValue, + onChange, + debounce = 300, + ...props +}: { + value: string | number + onChange: (value: string | number) => void + debounce?: number +} & Omit, 'onChange'>) { + const [value, setValue] = React.useState(initialValue) + + React.useEffect(() => { + setValue(initialValue) + }, [initialValue]) + + const debouncedOnChange = useDebouncedCallback(onChange, { wait: debounce }) + + return ( + { + setValue(event.currentTarget.value) + debouncedOnChange(event.currentTarget.value) + }} + /> + ) +} + +function App() { + const rerender = React.useReducer(() => ({}), {})[1] + const [rowSelection, setRowSelection] = React.useState({}) + const [sorting, setSorting] = React.useState([]) + const [columnFilters, setColumnFilters] = React.useState< + Array + >([]) + const [columnVisibility, setColumnVisibility] = React.useState({}) + const [columnSizing, setColumnSizing] = React.useState({}) + const [globalFilter, setGlobalFilter] = React.useState('') + const [columnPinning, setColumnPinning] = React.useState({ + left: ['select'], + right: ['actions'], + }) + const [grouping, setGrouping] = React.useState([]) + const [expanded, setExpanded] = React.useState({}) + const [data, setData] = React.useState(() => makeData(1_000)) + + const columns = React.useMemo( + () => + columnHelper.columns([ + columnHelper.display({ + id: 'select', + header: ({ table }) => ( + table.toggleAllPageRowsSelected(selected)} + /> + ), + cell: ({ row }) => ( + row.toggleSelected(selected)} + /> + ), + size: 64, + minSize: 64, + maxSize: 64, + enableSorting: false, + enableHiding: false, + enableResizing: false, + }), + columnHelper.accessor('firstName', { + id: 'firstName', + header: ({ column }) => ( + + ), + cell: (info) => ( + {String(info.getValue())} + ), + meta: { label: 'First Name', variant: 'text' }, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + header: ({ column }) => ( + + ), + cell: (info) => ( + {String(info.getValue())} + ), + meta: { label: 'Last Name', variant: 'text' }, + }), + columnHelper.accessor('age', { + id: 'age', + header: ({ column }) => ( + + ), + cell: (info) => ( + {String(info.getValue())} + ), + aggregationFn: 'mean', + aggregatedCell: ({ getValue }) => ( + + Avg: {Math.round(Number(getValue()) * 10) / 10} + + ), + meta: { label: 'Age', variant: 'number' }, + }), + columnHelper.accessor('email', { + id: 'email', + header: ({ column }) => ( + + ), + cell: (info) => ( + {info.cell.getValue()} + ), + meta: { label: 'Email', variant: 'text' }, + }), + columnHelper.accessor('status', { + id: 'status', + header: ({ column }) => ( + + ), + cell: (info) => { + const status = info.getValue() + return status ? : null + }, + aggregatedCell: () => null, + meta: { + label: 'Status', + variant: 'select', + options: statuses.map((status) => ({ + label: toSentenceCase(status), + value: status, + })), + }, + }), + columnHelper.accessor('department', { + id: 'department', + header: ({ column }) => ( + + ), + cell: (info) => { + const department = info.getValue() + return department ? ( + + ) : null + }, + aggregatedCell: () => null, + meta: { + label: 'Department', + variant: 'multi-select', + options: departments.map((department) => ({ + label: toSentenceCase(department), + value: department, + })), + }, + }), + columnHelper.accessor('joinDate', { + id: 'joinDate', + header: ({ column }) => ( + + ), + cell: (info) => formatDate(info.getValue()), + aggregationFn: 'min', + aggregatedCell: ({ getValue }) => { + const earliest = getValue() + return ( + + Earliest: {earliest ? formatDate(earliest) : '-'} + + ) + }, + meta: { label: 'Join Date', variant: 'date' }, + }), + columnHelper.accessor((row) => row.age, { + id: 'progress', + header: ({ column }) => ( + + ), + cell: (info) => { + const value = Math.min(100, Math.max(0, Number(info.getValue()))) + return ( + + {({ percentage }) => ( +
+
+
+ )} + + ) + }, + meta: { label: 'Profile Progress', variant: 'number' }, + }), + columnHelper.display({ + id: 'actions', + enableHiding: false, + cell: ({ row }) => , + size: 72, + minSize: 72, + maxSize: 72, + enableResizing: false, + }), + ]), + [], + ) + + const [columnOrder, setColumnOrder] = React.useState>(() => + columns.map((column) => column.id ?? ''), + ) + + const table = useTable( + { + _features, + _rowModels: { + coreRowModel: createCoreRowModel(), + filteredRowModel: createFilteredRowModel({ + ...filterFns, + fuzzy: fuzzyFilter, + }), + facetedRowModel: createFacetedRowModel(), + facetedUniqueValues: createFacetedUniqueValues(), + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + groupedRowModel: createGroupedRowModel(aggregationFns), + expandedRowModel: createExpandedRowModel(), + }, + columns, + data, + defaultColumn: { + minSize: 60, + maxSize: 800, + filterFn: dynamicFilterFn, + }, + globalFilterFn: 'fuzzy', + state: { + rowSelection, + sorting, + columnVisibility, + columnOrder, + columnSizing, + columnFilters, + globalFilter, + columnPinning, + grouping, + expanded, + }, + onSortingChange: setSorting, + onColumnVisibilityChange: setColumnVisibility, + onColumnOrderChange: setColumnOrder, + onColumnSizingChange: setColumnSizing, + onColumnFiltersChange: setColumnFilters, + onGlobalFilterChange: setGlobalFilter, + onColumnPinningChange: setColumnPinning, + onGroupingChange: setGrouping, + onExpandedChange: setExpanded, + getRowId: (row) => row.id, + enableRowSelection: true, + onRowSelectionChange: setRowSelection, + columnResizeMode: 'onChange', + debugTable: true, + }, + (state) => state, // default selector + ) + + const columnSizeVars = React.useMemo(() => { + const headers = table.getFlatHeaders() + const colSizes: Record = {} + for (const header of headers) { + colSizes[`--header-${header.id}-size`] = header.getSize() + colSizes[`--col-${header.column.id}-size`] = header.column.getSize() + } + return colSizes + }, [table.store.state.columnSizing]) + + const refreshData = () => setData(makeData(1_000)) + const stressTest = () => setData(makeData(200_000)) + + return ( + +
+
+
+
+ + + + + +
+
+ +
+
+ setGlobalFilter(String(value))} + placeholder="Search all columns..." + /> +
+
+ + + +
+
+ +
+
+ + + {table + .getHeaderGroups()[0] + ?.headers.filter((header) => header.column.getIsVisible()) + .map((header) => ( + + ))} + + + {table.getRowModel().rows.map((row) => { + const selected = row.getIsSelected() + return ( + + {row.getVisibleCells().map((cell) => ( + + {cell.getIsGrouped() ? ( + + ) : ( + + )} + + ))} + + ) + })} + + +
+ +
+
+
+
+ ) +} + +function ResizableHeaderCell({ + header, + table, +}: { + header: Header + table: { + FlexRender: React.ComponentType<{ + header: Header + }> + } +}) { + const sorting = React.useContext(SortingContext) + const sortDirection = getSortDirection(sorting, header.column.id) + + return ( + +
+ {header.isPlaceholder ? null : } + {header.column.getCanResize() ? ( +
header.column.resetSize()} + onMouseDown={header.getResizeHandler()} + onTouchStart={header.getResizeHandler()} + className={cx( + 'column-resizer', + header.column.getIsResizing() && 'bg-primary', + )} + /> + ) : null} +
+ + ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +ReactDOM.createRoot(rootElement).render( + + + , +) diff --git a/examples/react/kitchen-sink-react-aria/src/styles/globals.css b/examples/react/kitchen-sink-react-aria/src/styles/globals.css new file mode 100644 index 0000000000..8b1c37df43 --- /dev/null +++ b/examples/react/kitchen-sink-react-aria/src/styles/globals.css @@ -0,0 +1,592 @@ +@import 'tailwindcss' source('../../'); + +:root { + color-scheme: light; + --background: #ffffff; + --foreground: #18181b; + --muted: #71717a; + --border: #e4e4e7; + --surface: #ffffff; + --surface-subtle: #f4f4f5; + --primary: #2563eb; + --primary-foreground: #ffffff; +} + +.dark { + color-scheme: dark; + --background: #111113; + --foreground: #f4f4f5; + --muted: #a1a1aa; + --border: #27272a; + --surface: #18181b; + --surface-subtle: #242428; + --primary: #1493ff; + --primary-foreground: #ffffff; +} + +body { + margin: 0; + background: var(--background); + color: var(--foreground); + font-family: + Inter, + ui-sans-serif, + system-ui, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + sans-serif; +} + +.app-shell { + min-height: 100vh; + padding: 1.5rem; +} + +.control-panel { + border: 1px solid var(--border); + border-radius: 0.625rem; + background: var(--surface); + padding: 1rem; +} + +.control-panel-actions { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: flex-end; + gap: 0.625rem; +} + +.table-toolbar { + display: grid; + grid-template-columns: minmax(18rem, 1fr) auto; + align-items: center; + gap: 0.75rem; +} + +.table-toolbar-search { + min-width: 0; +} + +.table-toolbar-actions { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: flex-end; + gap: 0.5rem; +} + +.border-border { + border-color: var(--border); +} + +.bg-background { + background: var(--background); +} + +.bg-primary { + background: var(--primary); +} + +.bg-primary\/10 { + background: color-mix(in srgb, var(--primary) 10%, transparent); +} + +.text-muted { + color: var(--muted); +} + +.hover\:bg-muted\/40:hover { + background: color-mix(in srgb, var(--muted) 12%, transparent); +} + +.table-shell { + overflow: hidden; + border: 1px solid var(--border); + border-radius: 0.625rem; + background: var(--surface); + box-shadow: 0 1px 2px rgb(15 23 42 / 0.04); +} + +.table-scroll { + overflow: auto; +} + +.react-aria-Table, +.data-table { + width: 100%; + border-collapse: separate; + border-spacing: 0; + color: var(--foreground); + font-size: 0.875rem; + line-height: 1.35; +} + +.react-aria-Column, +.react-aria-Cell, +.data-table th, +.data-table td { + overflow: hidden; + border-bottom: 1px solid var(--border); + padding: 0.875rem 1rem; + text-align: left; + white-space: nowrap; + vertical-align: middle; +} + +.react-aria-Column, +.data-table th { + position: relative; + background: var(--surface-subtle); + color: var(--foreground); + font-weight: 600; +} + +.header-cell { + height: 3.5rem; +} + +.header-cell-content { + position: relative; + display: flex; + min-width: 0; + align-items: center; + gap: 0.5rem; + padding-right: 0.75rem; +} + +.header-cell.select-cell .header-cell-content, +.header-cell.action-cell .header-cell-content { + justify-content: center; + padding-right: 0; +} + +.data-table tbody tr { + height: 3.25rem; +} + +.react-aria-Row:last-child .react-aria-Cell, +.data-table tbody tr:last-child td { + border-bottom: 0; +} + +.select-cell { + width: 4rem; + min-width: 4rem; + padding-inline: 0.5rem; + text-align: center; +} + +.action-cell { + width: 4.5rem; + min-width: 4.5rem; + padding-inline: 0.75rem; + text-align: center; +} + +.react-aria-Cell.select-cell, +.data-table td.select-cell { + padding-inline: 0.5rem; +} + +.react-aria-Column[data-focused] { + outline: 2px solid color-mix(in srgb, var(--primary) 35%, transparent); + outline-offset: -2px; +} + +.react-aria-Row[data-hovered] { + background: color-mix(in srgb, var(--muted) 8%, transparent); +} + +.react-aria-Row[data-selected] { + background: color-mix(in srgb, var(--primary) 10%, transparent); +} + +.react-aria-Button { + display: inline-flex; + min-height: 2.25rem; + align-items: center; + justify-content: center; + gap: 0.375rem; + border: 1px solid var(--border); + border-radius: 0.375rem; + background: var(--surface); + padding: 0.375rem 0.75rem; + color: var(--foreground); + font-size: 0.875rem; + font-weight: 500; + outline: none; + transition: + border-color 120ms ease, + background 120ms ease, + box-shadow 120ms ease; +} + +.react-aria-Button[data-hovered] { + background: var(--surface-subtle); +} + +.react-aria-Button[data-pressed], +.react-aria-Button.is-active { + border-color: var(--primary); + background: var(--primary); + color: var(--primary-foreground); +} + +.react-aria-Button[data-disabled] { + cursor: default; + opacity: 0.45; +} + +.react-aria-Button[data-focused] { + box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 30%, transparent); +} + +.icon-button { + min-width: 2.25rem; + min-height: 2.25rem; + padding: 0.25rem 0.5rem; +} + +.header-sort-button, +.group-toggle-button { + min-height: 1.75rem; + border: 0; + background: transparent; + padding: 0; +} + +.sort-icon-unsorted { + opacity: 0; + transition: opacity 120ms ease; +} + +.header-sort-button:hover .sort-icon-unsorted, +.header-sort-button[data-hovered] .sort-icon-unsorted, +.header-sort-button:focus-visible .sort-icon-unsorted, +.header-sort-button[data-focused] .sort-icon-unsorted { + opacity: 1; +} + +.group-toggle-button { + justify-content: flex-start; + width: 100%; +} + +.column-resizer { + position: absolute; + top: -0.8rem; + right: -0.45rem; + width: 0.55rem; + height: calc(100% + 1.6rem); + cursor: col-resize; + touch-action: none; +} + +.column-resizer::after { + content: ''; + position: absolute; + top: 0.7rem; + right: 0.25rem; + bottom: 0.7rem; + width: 1px; + background: transparent; +} + +.column-resizer:hover::after, +.column-resizer.bg-primary::after { + background: var(--primary); +} + +.react-aria-Input, +.react-aria-Select .react-aria-Button { + width: 100%; +} + +.react-aria-Input { + min-height: 2.25rem; + border: 1px solid var(--border); + border-radius: 0.375rem; + background: var(--surface); + padding: 0.375rem 0.75rem; + color: var(--foreground); + font-size: 0.875rem; + outline: none; +} + +.react-aria-Input[data-focused], +.react-aria-Input:focus { + border-color: var(--primary); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 20%, transparent); +} + +.react-aria-Select { + display: grid; + gap: 0.25rem; +} + +.react-aria-Select .react-aria-Label, +label > span { + font-size: 0.875rem; + font-weight: 500; +} + +.react-aria-Popover { + min-width: var(--trigger-width); + overflow: auto; + border: 1px solid var(--border); + border-radius: 0.5rem; + background: var(--surface); + color: var(--foreground); + box-shadow: 0 12px 32px rgb(0 0 0 / 0.18); +} + +.react-aria-Dialog { + outline: none; +} + +.react-aria-Menu, +.react-aria-ListBox { + display: grid; + gap: 0.125rem; + padding: 0.25rem; + outline: none; +} + +.react-aria-MenuItem, +.react-aria-ListBoxItem { + border-radius: 0.375rem; + padding: 0.5rem 0.625rem; + color: var(--foreground); + outline: none; +} + +.react-aria-MenuItem[data-hovered], +.react-aria-MenuItem[data-focused], +.react-aria-ListBoxItem[data-hovered], +.react-aria-ListBoxItem[data-focused] { + background: var(--surface-subtle); +} + +.react-aria-MenuItem[data-disabled] { + color: var(--muted); + opacity: 0.55; +} + +.react-aria-ListBoxItem[data-selected] { + background: var(--primary); + color: var(--primary-foreground); +} + +.react-aria-Checkbox { + display: inline-flex; + align-items: center; + gap: 0.5rem; + color: var(--foreground); + outline: none; +} + +.react-aria-Checkbox::before { + content: ''; + width: 1rem; + height: 1rem; + flex: 0 0 auto; + border: 1px solid var(--border); + border-radius: 0.25rem; + background: var(--surface); +} + +.react-aria-Checkbox[data-selected]::before { + border-color: var(--primary); + background: + linear-gradient(var(--primary), var(--primary)), + linear-gradient(45deg, transparent 42%, white 42% 58%, transparent 58%), + linear-gradient(-45deg, transparent 50%, white 50% 64%, transparent 64%); +} + +.react-aria-Checkbox[data-indeterminate]::before { + border-color: var(--primary); + background: + linear-gradient(var(--primary), var(--primary)), + linear-gradient(white, white); + background-size: + 100% 100%, + 60% 2px; + background-position: center; + background-repeat: no-repeat; +} + +.react-aria-Checkbox[data-focused]::before { + box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 25%, transparent); +} + +.native-checkbox { + display: block; + width: 1rem; + height: 1rem; + margin-inline: auto; + accent-color: var(--primary); +} + +.native-checkbox:focus-visible { + outline: 2px solid color-mix(in srgb, var(--primary) 35%, transparent); + outline-offset: 2px; +} + +.react-aria-Switch { + display: inline-flex; + align-items: center; + outline: none; +} + +.switch-track { + position: relative; + display: inline-flex; + width: 2.4rem; + height: 1.35rem; + border: 1px solid var(--border); + border-radius: 999px; + background: var(--surface-subtle); + transition: background 120ms ease; +} + +.react-aria-Switch[data-selected] .switch-track { + background: var(--primary); +} + +.switch-thumb { + position: absolute; + top: 0.125rem; + left: 0.125rem; + width: 1rem; + height: 1rem; + border-radius: 999px; + background: white; + transition: transform 120ms ease; +} + +.react-aria-Switch[data-selected] .switch-thumb { + transform: translateX(1.05rem); +} + +.react-aria-Tooltip { + border: 1px solid var(--border); + border-radius: 0.375rem; + background: var(--foreground); + padding: 0.25rem 0.5rem; + color: var(--background); + font-size: 0.75rem; +} + +.status-badge { + display: inline-flex; + align-items: center; + border-radius: 999px; + padding: 0.125rem 0.5rem; + font-size: 0.75rem; + font-weight: 700; + line-height: 1.25; +} + +.status-active { + background: #dcfce7; + color: #166534; +} + +.status-inactive { + background: #fee2e2; + color: #991b1b; +} + +.status-pending { + background: #fef3c7; + color: #92400e; +} + +.dark .status-active { + background: #052e16; + color: #4ade80; +} + +.dark .status-inactive { + background: #450a0a; + color: #fca5a5; +} + +.dark .status-pending { + background: #422006; + color: #fbbf24; +} + +.progress-track { + height: 0.45rem; + overflow: hidden; + border-radius: 999px; + background: var(--surface-subtle); +} + +.progress-fill { + height: 100%; + border-radius: inherit; + background: var(--primary); +} + +.pagination { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.375rem; +} + +.page-ellipsis { + color: var(--muted); + padding-inline: 0.25rem; +} + +.table-pagination { + display: flex; + flex-direction: column; + gap: 0.875rem; + border-top: 1px solid var(--border); + padding: 1rem; +} + +.table-pagination-summary { + color: var(--muted); + font-size: 0.875rem; +} + +.table-pagination-controls { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.625rem; +} + +@media (min-width: 1024px) { + .table-pagination { + flex-direction: row; + align-items: center; + justify-content: space-between; + } + + .table-pagination-controls { + justify-content: flex-end; + } +} + +@media (max-width: 760px) { + .app-shell { + padding: 1rem; + } + + .table-toolbar { + grid-template-columns: 1fr; + } + + .control-panel-actions, + .table-toolbar-actions { + justify-content: flex-start; + } +} diff --git a/examples/react/kitchen-sink-react-aria/src/types/index.ts b/examples/react/kitchen-sink-react-aria/src/types/index.ts new file mode 100644 index 0000000000..4958d1736a --- /dev/null +++ b/examples/react/kitchen-sink-react-aria/src/types/index.ts @@ -0,0 +1,38 @@ +import type { + ColumnFilter, + TableFeatures, + filterFns, +} from '@tanstack/react-table' + +export type TableFilterFeatures = Pick< + TFeatures, + 'columnFilteringFeature' | 'columnFacetingFeature' +> + +export type FilterOperator = + | keyof typeof filterFns + | 'notIncludesString' + | 'notEqualsString' + | 'notEquals' + | 'greaterThan' + | 'notGreaterThan' + | 'greaterThanOrEqualTo' + | 'notGreaterThanOrEqualTo' + | 'lessThan' + | 'notLessThan' + | 'lessThanOrEqualTo' + | 'notLessThanOrEqualTo' + | 'isRelativeToToday' + | 'inRange' + | 'startsWith' + | 'endsWith' + | 'isEmpty' + | 'isNotEmpty' + +export type JoinOperator = 'and' | 'or' + +export interface ExtendedColumnFilter extends ColumnFilter { + filterId?: string + operator?: FilterOperator + joinOperator?: JoinOperator +} diff --git a/examples/react/kitchen-sink-react-aria/src/vite-env.d.ts b/examples/react/kitchen-sink-react-aria/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/kitchen-sink-react-aria/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/kitchen-sink-react-aria/tsconfig.json b/examples/react/kitchen-sink-react-aria/tsconfig.json new file mode 100644 index 0000000000..6714dcfb68 --- /dev/null +++ b/examples/react/kitchen-sink-react-aria/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "paths": { + "@/*": ["./src/*"] + }, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/react/kitchen-sink-react-aria/vite.config.js b/examples/react/kitchen-sink-react-aria/vite.config.js new file mode 100644 index 0000000000..27dc86dd10 --- /dev/null +++ b/examples/react/kitchen-sink-react-aria/vite.config.js @@ -0,0 +1,31 @@ +import path from 'node:path' +import { defineConfig } from 'vite' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' +import rollupReplace from '@rollup/plugin-replace' +import tailwindcss from '@tailwindcss/vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), + tailwindcss(), + ], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, +}) diff --git a/examples/react/kitchen-sink-shadcn-base/.gitignore b/examples/react/kitchen-sink-shadcn-base/.gitignore new file mode 100644 index 0000000000..18a5070e2a --- /dev/null +++ b/examples/react/kitchen-sink-shadcn-base/.gitignore @@ -0,0 +1,6 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local +!lib diff --git a/examples/react/pagination-controlled/README.md b/examples/react/kitchen-sink-shadcn-base/README.md similarity index 100% rename from examples/react/pagination-controlled/README.md rename to examples/react/kitchen-sink-shadcn-base/README.md diff --git a/examples/react/kitchen-sink-shadcn-base/components.json b/examples/react/kitchen-sink-shadcn-base/components.json new file mode 100644 index 0000000000..8bfc737f96 --- /dev/null +++ b/examples/react/kitchen-sink-shadcn-base/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/index.css", + "baseColor": "zinc", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/examples/react/kitchen-sink-shadcn-base/index.html b/examples/react/kitchen-sink-shadcn-base/index.html new file mode 100644 index 0000000000..1551290f72 --- /dev/null +++ b/examples/react/kitchen-sink-shadcn-base/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/react/kitchen-sink-shadcn-base/package.json b/examples/react/kitchen-sink-shadcn-base/package.json new file mode 100644 index 0000000000..d61e62f1ce --- /dev/null +++ b/examples/react/kitchen-sink-shadcn-base/package.json @@ -0,0 +1,47 @@ +{ + "name": "tanstack-react-table-example-kitchen-sink-shadcn-base", + "type": "module", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc", + "shadcn": "pnpm dlx shadcn@latest" + }, + "dependencies": { + "@base-ui/react": "^1.4.1", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", + "@faker-js/faker": "^10.4.0", + "@tailwindcss/vite": "^4.3.0", + "@tanstack/match-sorter-utils": "^9.0.0-alpha.4", + "@tanstack/react-pacer": "^0.22.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "1.1.1", + "date-fns": "^4.1.0", + "lucide-react": "^1.14.0", + "react": "^19.2.6", + "react-day-picker": "10.0.0", + "react-dom": "^19.2.6", + "tailwind-merge": "^3.5.0", + "tailwindcss": "^4.3.0", + "tw-animate-css": "^1.4.0" + }, + "devDependencies": { + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/react/kitchen-sink-shadcn-base/src/components/data-table/data-table-column-header.tsx b/examples/react/kitchen-sink-shadcn-base/src/components/data-table/data-table-column-header.tsx new file mode 100644 index 0000000000..ec2c356c85 --- /dev/null +++ b/examples/react/kitchen-sink-shadcn-base/src/components/data-table/data-table-column-header.tsx @@ -0,0 +1,162 @@ +'use client' + +import { + ArrowDown, + ArrowUp, + ChevronsUpDown, + EyeOff, + Group, + Pin, + PinOff, + Ungroup, +} from 'lucide-react' +import type { Column, RowData, TableFeatures } from '@tanstack/react-table' + +import { Button } from '@/components/ui/button' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu' +import { cn } from '@/lib/utils' + +/** + * Per-column header dropdown that consolidates the v9 column actions: + * sort asc/desc, group by, pin left/right, unpin, and hide. Items are + * conditionally rendered based on `column.getCan*()` so it's safe to use + * even when some features are not registered. + * + * Inspired by the shadcn data-table docs `DataTableColumnHeader` component + * (https://ui.shadcn.com/docs/components/radix/data-table) but extended to + * cover grouping and pinning since the kitchen-sink uses the full v9 surface. + */ +/** + * Features the dropdown queries on the column. Mirrors the generic shape the + * other `data-table-*` components use (TFeatures extends TableFeatures, + * narrowed via Pick at the use site). + */ +type ColumnHeaderFeatureKeys = + | 'columnGroupingFeature' + | 'columnPinningFeature' + | 'columnVisibilityFeature' + | 'rowSortingFeature' + +interface DataTableColumnHeaderProps< + TFeatures extends TableFeatures, + TData extends RowData, +> extends React.HTMLAttributes { + column: Column, TData> + title: string +} + +export function DataTableColumnHeader< + TFeatures extends TableFeatures, + TData extends RowData, +>({ column, title, className }: DataTableColumnHeaderProps) { + const canSort = column.getCanSort() + const canHide = column.getCanHide() + const canPin = column.getCanPin() + const canGroup = column.getCanGroup() + + // No actions available — render the title plain. + if (!canSort && !canHide && !canPin && !canGroup) { + return
{title}
+ } + + const sorted = canSort ? column.getIsSorted() : false + const pinned = canPin ? column.getIsPinned() : false + const grouped = canGroup ? column.getIsGrouped() : false + + return ( +
+ + + } + > + {title} + {sorted === 'desc' ? ( + + ) : sorted === 'asc' ? ( + + ) : canSort ? ( + + ) : null} + + + {canSort && ( + <> + column.toggleSorting(false)}> + + Asc + + column.toggleSorting(true)}> + + Desc + + + )} + {canGroup && ( + <> + {canSort ? : null} + + {grouped ? ( + <> + + Ungroup + + ) : ( + <> + + Group by + + )} + + + )} + {canPin && ( + <> + {canSort || canGroup ? : null} + column.pin('left')} + disabled={pinned === 'left'} + > + + Pin left + + column.pin('right')} + disabled={pinned === 'right'} + > + + Pin right + + {pinned ? ( + column.pin(false)}> + + Unpin + + ) : null} + + )} + {canHide && ( + <> + + column.toggleVisibility(false)}> + + Hide + + + )} + + +
+ ) +} diff --git a/examples/react/kitchen-sink-shadcn-base/src/components/data-table/data-table-filter-list.tsx b/examples/react/kitchen-sink-shadcn-base/src/components/data-table/data-table-filter-list.tsx new file mode 100644 index 0000000000..25f80fd99d --- /dev/null +++ b/examples/react/kitchen-sink-shadcn-base/src/components/data-table/data-table-filter-list.tsx @@ -0,0 +1,971 @@ +'use client' + +import * as React from 'react' +import { format } from 'date-fns' +import { + CalendarIcon, + Check, + ChevronsUpDown, + ListFilter, + Trash2, +} from 'lucide-react' +import type { + ExtendedColumnFilter, + FilterOperator, + TableFilterFeatures, +} from '@/types' +import type { + Column, + ColumnMeta, + RowData, + Table, + TableFeatures, +} from '@tanstack/react-table' + +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui/popover' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' +import { Calendar } from '@/components/ui/calendar' +import { getFilterOperators } from '@/lib/data-table' +import { cn } from '@/lib/utils' +import { + Faceted, + FacetedBadgeList, + FacetedContent, + FacetedEmpty, + FacetedGroup, + FacetedInput, + FacetedItem, + FacetedList, + FacetedTrigger, +} from '@/components/ui/faceted' +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from '@/components/ui/command' + +// TODO: column.getFacetedUniqueValues() is broken rn, remove this once it's fixed +function createManualFacetedValues( + table: Table, TData>, + columnId: string, +): Map { + const facetedValues = new Map() + + const rows = table.getPreFilteredRowModel().flatRows + + for (const row of rows) { + const value = row.getValue(columnId) + if (value !== undefined && value !== null) { + const count = facetedValues.get(value) ?? 0 + facetedValues.set(value, count + 1) + } + } + + return facetedValues +} + +function getColumnOptions< + TFeatures extends TableFeatures, + TData extends RowData, +>({ + column, + table, +}: { + column: Column, TData> + table: Table, TData> +}): Array<{ label: string; value: string; count?: number }> { + const customOptions = column.columnDef.meta?.options + + if (customOptions) return customOptions + + let uniqueValues: Map + try { + uniqueValues = column.getFacetedUniqueValues() + + if (!(uniqueValues instanceof Map)) { + uniqueValues = createManualFacetedValues(table, column.id) + } + } catch (_err) { + uniqueValues = createManualFacetedValues(table, column.id) + } + + return Array.from(uniqueValues.entries()).map(([value, count]) => ({ + label: String(value), + value: String(value), + count, + })) +} + +interface DataTableFilterListProps< + TFeatures extends TableFeatures, + TData extends RowData, +> { + table: Table, TData> + columnFilters: Array + onColumnFiltersChange: (filters: Array) => void +} + +export function DataTableFilterList< + TFeatures extends TableFeatures, + TData extends RowData, +>({ + table, + columnFilters, + onColumnFiltersChange, +}: DataTableFilterListProps) { + const id = React.useId() + const labelId = React.useId() + const descriptionId = React.useId() + const listId = React.useId() + const [open, setOpen] = React.useState(false) + + const filterableColumns = React.useMemo( + () => table.getAllColumns().filter((column) => column.getCanFilter()), + [table], + ) + + const getColumnFilterVariant = React.useCallback( + ( + column: Column, TData>, + ): ColumnMeta['variant'] => { + if (column.columnDef.meta?.variant) { + return column.columnDef.meta.variant + } + + const firstValue = table + .getPreFilteredRowModel() + .flatRows[0]?.getValue(column.id) + + if (Array.isArray(firstValue)) return 'multi-select' + if (typeof firstValue === 'number') return 'number' + if (firstValue instanceof Date) return 'date' + if (column.columnDef.meta?.variant === 'select') return 'select' + + return 'text' + }, + [table], + ) + + const onFilterAddImpl = React.useCallback( + (columnId: string): ExtendedColumnFilter | null => { + const column = filterableColumns.find((col) => col.id === columnId) + if (!column) return null + + const filterVariant = getColumnFilterVariant(column) + const operators = getFilterOperators(filterVariant ?? 'text') + const defaultOperator = operators[0].value + + return { + id: columnId, + value: filterVariant === 'multi-select' ? [] : '', + operator: defaultOperator, + filterId: crypto.randomUUID(), + joinOperator: 'and', + } + }, + [filterableColumns, getColumnFilterVariant], + ) + + const onFilterAdd = React.useCallback(() => { + const firstFilterableColumn = filterableColumns[0] + + const newFilter = onFilterAddImpl(firstFilterableColumn.id) + if (newFilter) { + onColumnFiltersChange([...columnFilters, newFilter]) + } + }, [columnFilters, onFilterAddImpl, filterableColumns, onColumnFiltersChange]) + + const onFilterUpdate = React.useCallback( + ( + filterId: string, + updates: Partial>, + ) => { + const newFilters = columnFilters.map((filter) => { + if (filter.filterId === filterId) { + if (updates.id) { + const newColumn = filterableColumns.find( + (col) => col.id === updates.id, + ) + if (newColumn) { + const filterVariant = getColumnFilterVariant(newColumn) + const operators = getFilterOperators(filterVariant ?? 'text') + const defaultOperator = operators[0].value + return { + ...filter, + ...updates, + operator: defaultOperator, + value: filterVariant === 'multi-select' ? [] : '', + } + } + } + + if (updates.operator && filter.value) { + const column = filterableColumns.find((col) => col.id === filter.id) + if (column && getColumnFilterVariant(column) === 'date') { + const currentValue = filter.value + if ( + updates.operator === 'inRange' && + !Array.isArray(currentValue) + ) { + return { + ...filter, + ...updates, + value: [currentValue, undefined], + } + } else if ( + updates.operator !== 'inRange' && + Array.isArray(currentValue) + ) { + return { + ...filter, + ...updates, + value: currentValue[0] ?? '', + } + } + } + } + + return { ...filter, ...updates } + } + return filter + }) + onColumnFiltersChange(newFilters) + }, + [ + columnFilters, + filterableColumns, + getColumnFilterVariant, + getFilterOperators, + onColumnFiltersChange, + ], + ) + + const onFilterRemove = React.useCallback( + (filterId: string) => { + const newFilters = columnFilters.filter((filter) => { + return filter.filterId !== filterId + }) + onColumnFiltersChange(newFilters) + }, + [columnFilters, onColumnFiltersChange], + ) + + const onFilterInputRender = React.useCallback( + ({ + column, + operator, + filterId, + inputId, + }: { + column: Column, TData> + operator: FilterOperator + filterId: string + inputId: string + }) => { + const filterVariant = getColumnFilterVariant(column) ?? 'text' + const currentFilter = columnFilters.find( + (filter) => filter.filterId === filterId, + ) + const columnLabel = column.columnDef.meta?.label ?? column.id + + switch (filterVariant) { + case 'date': + if (operator === 'inRange') { + const currentValue = Array.isArray(currentFilter?.value) + ? currentFilter.value + : [currentFilter?.value, undefined] + + const dateRange = + currentValue[0] || currentValue[1] + ? { + from: currentValue[0] + ? new Date(currentValue[0]) + : undefined, + to: currentValue[1] ? new Date(currentValue[1]) : undefined, + } + : undefined + + return ( +
+ + svg]:size-3.5', + !dateRange && 'text-muted-foreground', + )} + onPointerDown={(event) => { + const target = event.target + if (!(target instanceof HTMLElement)) return + if (target.hasPointerCapture(event.pointerId)) { + target.releasePointerCapture(event.pointerId) + } + + if ( + event.button === 0 && + event.ctrlKey === false && + event.pointerType === 'mouse' + ) { + event.preventDefault() + } + }} + /> + } + > + + {dateRange?.from ? ( + dateRange.to ? ( + <> + {format(dateRange.from, 'LLL dd, y')} -{' '} + {format(dateRange.to, 'LLL dd, y')} + + ) : ( + format(dateRange.from, 'LLL dd, y') + ) + ) : ( + Select date range + )} + + { + document.getElementById(inputId)?.focus({ + preventScroll: true, + }) + }} + > + { + if (filterId) { + onFilterUpdate(filterId, { + value: [ + date?.from ? date.from.toISOString() : undefined, + date?.to ? date.to.toISOString() : undefined, + ], + operator, + }) + } + }} + numberOfMonths={2} + autoFocus + /> + + +
+ ) + } + + const selectedDate = currentFilter?.value + ? new Date(currentFilter.value as string) + : undefined + + return ( + + svg]:size-3.5', + !currentFilter?.value && 'text-muted-foreground', + )} + onPointerDown={(event) => { + const target = event.target + if (!(target instanceof HTMLElement)) return + if (target.hasPointerCapture(event.pointerId)) { + target.releasePointerCapture(event.pointerId) + } + + if ( + event.button === 0 && + event.ctrlKey === false && + event.pointerType === 'mouse' + ) { + event.preventDefault() + } + }} + /> + } + > + + {currentFilter?.value ? ( + format(new Date(currentFilter.value as string), 'PP') + ) : ( + Pick a date + )} + + { + document.getElementById(inputId)?.focus({ + preventScroll: true, + }) + }} + > + { + if (filterId) { + onFilterUpdate(filterId, { + value: date ? date.toISOString() : undefined, + operator, + }) + } + }} + autoFocus + /> + + + ) + case 'number': + if (operator === 'inRange') { + const currentValue = Array.isArray(currentFilter?.value) + ? currentFilter.value + : [currentFilter?.value, undefined] + + return ( +
+ { + if (filterId) { + onFilterUpdate(filterId, { + value: [ + event.target.value === '' + ? undefined + : Number(event.target.value), + currentValue[1] ?? undefined, + ], + operator, + }) + } + }} + /> + { + if (filterId) { + onFilterUpdate(filterId, { + value: [ + currentValue[0] ?? undefined, + event.target.value === '' + ? undefined + : Number(event.target.value), + ], + operator, + }) + } + }} + /> +
+ ) + } + + return ( + { + if (filterId) { + onFilterUpdate(filterId, { + value: + event.target.value === '' + ? '' + : Number(event.target.value), + operator, + }) + } + }} + /> + ) + case 'select': + const selectOptions = getColumnOptions({ column, table }) + + return ( + { + if (filterId) { + onFilterUpdate(filterId, { value }) + } + }} + > + + } + > + + + + + + No options found. + + {selectOptions.map((option) => ( + + {option.label} + {option.count && ( + + {option.count} + + )} + + ))} + + + + + ) + + case 'multi-select': + const multiSelectOptions = getColumnOptions({ column, table }) + const selectedValues = Array.isArray(currentFilter?.value) + ? currentFilter.value + : [] + + return ( + { + if (filterId) { + onFilterUpdate(filterId, { value }) + } + }} + > + + } + > + + + + + + No options found. + + {multiSelectOptions.map((option) => ( + + {option.label} + {option.count && ( + + {option.count} + + )} + + ))} + + + + + ) + + default: + if (operator === 'isEmpty' || operator === 'isNotEmpty') { + return ( +
+ ) + } + + return ( + { + if (filterId) { + onFilterUpdate(filterId, { + value: event.target.value, + operator, + }) + } + }} + /> + ) + } + }, + [getColumnFilterVariant, columnFilters, onFilterUpdate], + ) + + const onFilterRender = React.useCallback( + ({ filter, index }: { filter: ExtendedColumnFilter; index: number }) => { + const column = table.getColumn(filter.id) + if (!column || !filter.filterId) return null + + const filterVariant = getColumnFilterVariant(column) ?? 'text' + const operators = getFilterOperators(filterVariant) + const filterItemId = `${id}-filter-${filter.filterId}` + const triggerId = `${filterItemId}-trigger` + const joinOperatorListboxId = `${filterItemId}-join-operator-listbox` + const fieldListboxId = `${filterItemId}-field-listbox` + const operatorListboxId = `${filterItemId}-operator-listbox` + const inputId = `${filterItemId}-input` + + return ( +
+ {index === 0 ? ( + + Where + + ) : index === 1 ? ( + + ) : ( + + {filter.joinOperator} + + )} + + { + const target = event.target + if (!(target instanceof HTMLElement)) return + if (target.hasPointerCapture(event.pointerId)) { + target.releasePointerCapture(event.pointerId) + } + + if ( + event.button === 0 && + event.ctrlKey === false && + event.pointerType === 'mouse' + ) { + event.preventDefault() + } + }} + /> + } + > + + {column.columnDef.meta?.label ?? column.id} + + + + + document + .getElementById(triggerId) + ?.focus({ preventScroll: true }) + } + > + + + + No column found. + + {filterableColumns.map((col) => ( + { + if (!filter.filterId) return + onFilterUpdate(filter.filterId, { id: value }) + }} + > + + {col.columnDef.meta?.label ?? col.id} + + + ))} + + + + + + + {onFilterInputRender({ + column, + operator: filter.operator ?? 'includesString', + filterId: filter.filterId, + inputId, + })} + +
+ ) + }, + [ + table, + filterableColumns, + getColumnFilterVariant, + onFilterInputRender, + onFilterUpdate, + onFilterRemove, + ], + ) + + return ( + + event.currentTarget.focus()} + onPointerDown={(event) => { + const target = event.target + if (!(target instanceof HTMLElement)) return + if (target.hasPointerCapture(event.pointerId)) { + target.releasePointerCapture(event.pointerId) + } + + if ( + event.button === 0 && + event.ctrlKey === false && + event.pointerType === 'mouse' + ) { + event.preventDefault() + } + }} + /> + } + > + + Filter + {columnFilters.length > 0 && ( + + {columnFilters.length} + + )} + + +
+

+ Filters +

+

0 && 'sr-only', + )} + > + {columnFilters.length > 0 + ? 'Modify filters to refine your results.' + : 'Add filters to refine your results.'} +

+
+ {columnFilters.length > 0 && ( +
+ {columnFilters.map((filter, index) => ( + + {onFilterRender({ filter, index })} + + ))} +
+ )} +
+ + {columnFilters.length > 0 && ( + + )} +
+
+
+ ) +} diff --git a/examples/react/kitchen-sink-shadcn-base/src/components/data-table/data-table-pagination.tsx b/examples/react/kitchen-sink-shadcn-base/src/components/data-table/data-table-pagination.tsx new file mode 100644 index 0000000000..a5d573d22e --- /dev/null +++ b/examples/react/kitchen-sink-shadcn-base/src/components/data-table/data-table-pagination.tsx @@ -0,0 +1,107 @@ +'use client' + +import { + ChevronLeft, + ChevronRight, + ChevronsLeft, + ChevronsRight, +} from 'lucide-react' +import type { RowData, Table, TableFeatures } from '@tanstack/react-table' + +import { Button } from '@/components/ui/button' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' + +interface DataTablePaginationProps { + table: Table, RowData> + pageSizeOptions?: Array +} + +export function DataTablePagination({ + table, + pageSizeOptions = [10, 20, 30, 40, 50], +}: DataTablePaginationProps) { + return ( +
+
+ {table.getFilteredSelectedRowModel().rows.length.toLocaleString()} of{' '} + {table.getFilteredRowModel().rows.length.toLocaleString()} row(s) + selected. +
+
+
+

Rows per page

+ +
+
+ Page {(table.store.state.pagination.pageIndex + 1).toLocaleString()}{' '} + of {table.getPageCount().toLocaleString()} +
+
+ + + + +
+
+
+ ) +} diff --git a/examples/react/kitchen-sink-shadcn-base/src/components/data-table/data-table-sort-list.tsx b/examples/react/kitchen-sink-shadcn-base/src/components/data-table/data-table-sort-list.tsx new file mode 100644 index 0000000000..12fec45020 --- /dev/null +++ b/examples/react/kitchen-sink-shadcn-base/src/components/data-table/data-table-sort-list.tsx @@ -0,0 +1,382 @@ +'use client' + +import * as React from 'react' +import { + ArrowDownUp, + Check, + ChevronsUpDown, + GripVertical, + Trash2, +} from 'lucide-react' +import type { + ColumnSort, + RowData, + SortDirection, + SortingState, + Table, + TableFeatures, +} from '@tanstack/react-table' + +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from '@/components/ui/command' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui/popover' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' +import { + Sortable, + SortableContent, + SortableItem, + SortableItemHandle, + SortableOverlay, +} from '@/components/ui/sortable' +import { cn } from '@/lib/utils' + +interface DataTableSortListProps< + TFeatures extends TableFeatures, + TData extends RowData, +> { + table: Table, TData> + sorting: SortingState + onSortingChange: (sorting: SortingState) => void +} + +export function DataTableSortList< + TFeatures extends TableFeatures, + TData extends RowData, +>({ + table, + sorting, + onSortingChange, +}: DataTableSortListProps) { + const labelId = React.useId() + const descriptionId = React.useId() + const listId = React.useId() + const [open, setOpen] = React.useState(false) + + const sortableColumns = React.useMemo( + () => table.getAllColumns().filter((column) => column.getCanSort()), + [table], + ) + + const onColumnSelect = React.useCallback( + (currentSortId: string, newColumnId: string) => { + const newSorting = sorting.map((s) => + s.id === currentSortId ? { ...s, id: newColumnId } : s, + ) + table.setSorting(newSorting) + }, + [sorting, table], + ) + + const onSortAdd = React.useCallback(() => { + const firstAvailableColumn = sortableColumns.find( + (col) => !sorting.some((s) => s.id === col.id), + ) + if (firstAvailableColumn) { + table.setSorting([ + ...sorting, + { id: firstAvailableColumn.id, desc: false }, + ]) + } + }, [sorting, sortableColumns, table]) + + const onSortUpdate = React.useCallback( + (sortId: string, updates: Partial>) => { + const newSorting = sorting.map((s) => + s.id === sortId ? { ...s, ...updates } : s, + ) + table.setSorting(newSorting) + }, + [sorting, table], + ) + + const onSortRemove = React.useCallback( + (sortId: string) => { + const newSorting = sorting.filter((s) => s.id !== sortId) + table.setSorting(newSorting) + }, + [sorting, table], + ) + + return ( + item.id} + > + + event.currentTarget.focus()} + onPointerDown={(event) => { + const target = event.target + if (!(target instanceof HTMLElement)) return + if (target.hasPointerCapture(event.pointerId)) { + target.releasePointerCapture(event.pointerId) + } + + if ( + event.button === 0 && + event.ctrlKey === false && + event.pointerType === 'mouse' + ) { + event.preventDefault() + } + }} + /> + } + > + + Sort + {sorting.length > 0 && ( + + {sorting.length} + + )} + + +
+

+ {sorting.length > 0 ? 'Sort by' : 'No sorting applied'} +

+

0 && 'sr-only', + )} + > + {sorting.length > 0 + ? 'Modify sorting to organize your results.' + : 'Add sorting to organize your results.'} +

+
+ {sorting.length > 0 ? ( + +
+ {sorting.map((sort, index) => { + const columnTitle = + sortableColumns.find((col) => col.id === sort.id)?.columnDef + .meta?.label ?? sort.id + const sortItemId = `${listId}-item-${sort.id}` + const triggerId = `${listId}-${index}-trigger` + const fieldListboxId = `${sortItemId}-field-listbox` + const operatorListboxId = `${sortItemId}-operator-listbox` + + return ( + +
+ + { + const target = event.target + if (!(target instanceof HTMLElement)) return + if ( + target.hasPointerCapture(event.pointerId) + ) { + target.releasePointerCapture( + event.pointerId, + ) + } + + if ( + event.button === 0 && + event.ctrlKey === false && + event.pointerType === 'mouse' + ) { + event.preventDefault() + } + }} + /> + } + > + {columnTitle} + + + + document + .getElementById(triggerId) + ?.focus({ preventScroll: true }) + } + > + + + + No column found. + + {sortableColumns + .filter( + (column) => + !sorting.some( + (s) => + s.id === column.id && + s.id !== sort.id, + ), + ) + .map((column) => ( + + onColumnSelect(sort.id, column.id) + } + > + + {column.columnDef.meta?.label ?? + column.id} + + + ))} + + + + + + + + + + +
+
+ ) + })} +
+
+ ) : null} +
+ + {sorting.length > 0 && ( + + )} +
+
+
+ +
+
+
+
+
+
+ + + ) +} diff --git a/examples/react/kitchen-sink-shadcn-base/src/components/data-table/data-table-view-options.tsx b/examples/react/kitchen-sink-shadcn-base/src/components/data-table/data-table-view-options.tsx new file mode 100644 index 0000000000..5c2598b34f --- /dev/null +++ b/examples/react/kitchen-sink-shadcn-base/src/components/data-table/data-table-view-options.tsx @@ -0,0 +1,173 @@ +'use client' + +import * as React from 'react' +import { Check, ChevronsUpDown, GripVertical, Settings2 } from 'lucide-react' +import type { + ColumnOrderState, + RowData, + Table, + TableFeatures, +} from '@tanstack/react-table' + +import { Button } from '@/components/ui/button' +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, +} from '@/components/ui/command' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui/popover' +import { cn } from '@/lib/utils' +import { + Sortable, + SortableContent, + SortableItem, + SortableItemHandle, + SortableOverlay, +} from '@/components/ui/sortable' + +interface DataTableViewOptionsProps< + TFeatures extends TableFeatures, + TRowData extends RowData, +> { + table: Table, TRowData> + columnOrder: ColumnOrderState + onColumnOrderChange: (columnOrder: ColumnOrderState) => void +} + +export function DataTableViewOptions< + TFeatures extends TableFeatures, + TRowData extends RowData, +>({ + table, + columnOrder, + onColumnOrderChange, +}: DataTableViewOptionsProps) { + const triggerRef = React.useRef(null) + + return ( + + + { + // prevent implicit pointer capture + // https://www.w3.org/TR/pointerevents3/#implicit-pointer-capture + const target = event.target + if (!(target instanceof HTMLElement)) return + if (target.hasPointerCapture(event.pointerId)) { + target.releasePointerCapture(event.pointerId) + } + + if ( + event.button === 0 && + event.ctrlKey === false && + event.pointerType === 'mouse' + ) { + // prevent trigger from stealing focus from the active item after opening. + event.preventDefault() + } + }} + /> + } + > + + View + + + triggerRef.current?.focus()} + > + + + + + No columns found. + + {table + .getAllColumns() + .sort((a, b) => { + const aIndex = columnOrder.indexOf(a.id) + const bIndex = columnOrder.indexOf(b.id) + return aIndex - bIndex + }) + .filter( + (column) => typeof column.accessorFn !== 'undefined', + ) + .map((column) => ( + + + column.toggleVisibility(!column.getIsVisible()) + } + > + + + {column.columnDef.meta?.label ?? column.id} + + + + + + + ))} + + + +
+ table.toggleAllColumnsVisible(false)} + className="w-full justify-center border" + > + Hide All + + table.toggleAllColumnsVisible(true)} + className="w-full justify-center border" + > + Show All + +
+
+
+
+
+
+
+ +
+ + + ) +} diff --git a/examples/react/kitchen-sink-shadcn-base/src/components/mode-toggle.tsx b/examples/react/kitchen-sink-shadcn-base/src/components/mode-toggle.tsx new file mode 100644 index 0000000000..8b30b9127c --- /dev/null +++ b/examples/react/kitchen-sink-shadcn-base/src/components/mode-toggle.tsx @@ -0,0 +1,37 @@ +import { Moon, Sun } from 'lucide-react' + +import { Button } from '@/components/ui/button' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu' +import { useTheme } from '@/components/theme-provider' + +export function ModeToggle() { + const { setTheme } = useTheme() + + return ( + + } + > + + + Toggle theme + + + setTheme('light')}> + Light + + setTheme('dark')}> + Dark + + setTheme('system')}> + System + + + + ) +} diff --git a/examples/react/kitchen-sink-shadcn-base/src/components/theme-provider.tsx b/examples/react/kitchen-sink-shadcn-base/src/components/theme-provider.tsx new file mode 100644 index 0000000000..6bec259585 --- /dev/null +++ b/examples/react/kitchen-sink-shadcn-base/src/components/theme-provider.tsx @@ -0,0 +1,75 @@ +import { createContext, useContext, useEffect, useState } from 'react' + +type Theme = 'dark' | 'light' | 'system' + +interface ThemeProviderProps { + children: React.ReactNode + defaultTheme?: Theme + storageKey?: string +} + +interface ThemeProviderState { + theme: Theme + setTheme: (theme: Theme) => void +} + +const initialState: ThemeProviderState = { + theme: 'system', + setTheme: () => null, +} + +const ThemeProviderContext = createContext(initialState) + +function isTheme(value: string | null): value is Theme { + return value === 'dark' || value === 'light' || value === 'system' +} + +export function ThemeProvider({ + children, + defaultTheme = 'system', + storageKey = 'vite-ui-theme', + ...props +}: ThemeProviderProps) { + const [theme, setTheme] = useState(() => { + const storedTheme = localStorage.getItem(storageKey) + + return isTheme(storedTheme) ? storedTheme : defaultTheme + }) + + useEffect(() => { + const root = window.document.documentElement + + root.classList.remove('light', 'dark') + + if (theme === 'system') { + const systemTheme = window.matchMedia('(prefers-color-scheme: dark)') + .matches + ? 'dark' + : 'light' + root.classList.add(systemTheme) + return + } + + root.classList.add(theme) + }, [theme]) + + const value = { + theme, + setTheme: (theme: Theme) => { + localStorage.setItem(storageKey, theme) + setTheme(theme) + }, + } + + return ( + + {children} + + ) +} + +export const useTheme = () => { + const context = useContext(ThemeProviderContext) + + return context +} diff --git a/examples/react/kitchen-sink-shadcn-base/src/components/ui/badge.tsx b/examples/react/kitchen-sink-shadcn-base/src/components/ui/badge.tsx new file mode 100644 index 0000000000..0d64314c1a --- /dev/null +++ b/examples/react/kitchen-sink-shadcn-base/src/components/ui/badge.tsx @@ -0,0 +1,62 @@ +import * as React from 'react' +import { cva } from 'class-variance-authority' +import type { VariantProps } from 'class-variance-authority' + +import { cn } from '@/lib/utils' + +const badgeVariants = cva( + 'inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3', + { + variants: { + variant: { + default: 'bg-primary text-primary-foreground [a&]:hover:bg-primary/90', + secondary: + 'bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90', + destructive: + 'bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90', + outline: + 'border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground', + ghost: '[a&]:hover:bg-accent [a&]:hover:text-accent-foreground', + link: 'text-primary underline-offset-4 [a&]:hover:underline', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +) + +function Badge({ + className, + variant = 'default', + asChild = false, + children, + ...props +}: React.ComponentProps<'span'> & + VariantProps & { asChild?: boolean }) { + const badgeClassName = cn(badgeVariants({ variant }), className) + + if (asChild && React.isValidElement(children)) { + const child = children as React.ReactElement<{ className?: string }> + + return React.cloneElement(child, { + ...props, + 'data-slot': 'badge', + 'data-variant': variant, + className: cn(badgeClassName, child.props.className), + } as React.HTMLAttributes) + } + + return ( + + {children} + + ) +} + +export { Badge, badgeVariants } diff --git a/examples/react/kitchen-sink-shadcn-base/src/components/ui/button.tsx b/examples/react/kitchen-sink-shadcn-base/src/components/ui/button.tsx new file mode 100644 index 0000000000..5d1cc6f15b --- /dev/null +++ b/examples/react/kitchen-sink-shadcn-base/src/components/ui/button.tsx @@ -0,0 +1,57 @@ +import { Button as ButtonPrimitive } from '@base-ui/react/button' +import { cva } from 'class-variance-authority' +import type { VariantProps } from 'class-variance-authority' + +import { cn } from '@/lib/utils' + +const buttonVariants = cva( + "group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-xs/relaxed font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + { + variants: { + variant: { + default: 'bg-primary text-primary-foreground hover:bg-primary/80', + outline: + 'border-border hover:bg-input/50 hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:bg-input/30', + secondary: + 'bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground', + ghost: + 'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50', + destructive: + 'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40', + link: 'text-primary underline-offset-4 hover:underline', + }, + size: { + default: + "h-7 gap-1 px-2 text-xs/relaxed has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5", + xs: "h-5 gap-1 rounded-sm px-2 text-[0.625rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-2.5", + sm: "h-6 gap-1 px-2 text-xs/relaxed has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3", + lg: "h-8 gap-1 px-2.5 text-xs/relaxed has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-4", + icon: "size-7 [&_svg:not([class*='size-'])]:size-3.5", + 'icon-xs': "size-5 rounded-sm [&_svg:not([class*='size-'])]:size-2.5", + 'icon-sm': "size-6 [&_svg:not([class*='size-'])]:size-3", + 'icon-lg': "size-8 [&_svg:not([class*='size-'])]:size-4", + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + }, +) + +function Button({ + className, + variant = 'default', + size = 'default', + ...props +}: ButtonPrimitive.Props & VariantProps) { + return ( + + ) +} + +export { Button, buttonVariants } diff --git a/examples/react/kitchen-sink-shadcn-base/src/components/ui/calendar.tsx b/examples/react/kitchen-sink-shadcn-base/src/components/ui/calendar.tsx new file mode 100644 index 0000000000..0ddc93c872 --- /dev/null +++ b/examples/react/kitchen-sink-shadcn-base/src/components/ui/calendar.tsx @@ -0,0 +1,215 @@ +import * as React from 'react' +import { + ChevronDownIcon, + ChevronLeftIcon, + ChevronRightIcon, +} from 'lucide-react' +import { DayPicker, getDefaultClassNames } from 'react-day-picker' +import type { DayButton } from 'react-day-picker' + +import { cn } from '@/lib/utils' +import { Button, buttonVariants } from '@/components/ui/button' + +function Calendar({ + className, + classNames, + showOutsideDays = true, + captionLayout = 'label', + buttonVariant = 'ghost', + formatters, + components, + ...props +}: React.ComponentProps & { + buttonVariant?: React.ComponentProps['variant'] +}) { + const defaultClassNames = getDefaultClassNames() + + return ( + svg]:rotate-180`, + String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`, + className, + )} + captionLayout={captionLayout} + formatters={{ + formatMonthDropdown: (date) => + date.toLocaleString('default', { month: 'short' }), + ...formatters, + }} + classNames={{ + root: cn('w-fit', defaultClassNames.root), + months: cn( + 'relative flex flex-col gap-4 md:flex-row', + defaultClassNames.months, + ), + month: cn('flex w-full flex-col gap-4', defaultClassNames.month), + nav: cn( + 'absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1', + defaultClassNames.nav, + ), + button_previous: cn( + buttonVariants({ variant: buttonVariant }), + 'size-(--cell-size) p-0 select-none aria-disabled:opacity-50', + defaultClassNames.button_previous, + ), + button_next: cn( + buttonVariants({ variant: buttonVariant }), + 'size-(--cell-size) p-0 select-none aria-disabled:opacity-50', + defaultClassNames.button_next, + ), + month_caption: cn( + 'flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)', + defaultClassNames.month_caption, + ), + dropdowns: cn( + 'flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium', + defaultClassNames.dropdowns, + ), + dropdown_root: cn( + 'relative rounded-md border border-input shadow-xs has-focus:border-ring has-focus:ring-[3px] has-focus:ring-ring/50', + defaultClassNames.dropdown_root, + ), + dropdown: cn( + 'absolute inset-0 bg-popover opacity-0', + defaultClassNames.dropdown, + ), + caption_label: cn( + 'font-medium select-none', + captionLayout === 'label' + ? 'text-sm' + : 'flex h-8 items-center gap-1 rounded-md pr-1 pl-2 text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground', + defaultClassNames.caption_label, + ), + month_grid: 'w-full border-collapse', + weekdays: cn('flex', defaultClassNames.weekdays), + weekday: cn( + 'flex-1 rounded-md text-[0.8rem] font-normal text-muted-foreground select-none', + defaultClassNames.weekday, + ), + week: cn('mt-2 flex w-full', defaultClassNames.week), + week_number_header: cn( + 'w-(--cell-size) select-none', + defaultClassNames.week_number_header, + ), + week_number: cn( + 'text-[0.8rem] text-muted-foreground select-none', + defaultClassNames.week_number, + ), + day: cn( + 'group/day relative aspect-square h-full w-full p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-r-md', + props.showWeekNumber + ? '[&:nth-child(2)[data-selected=true]_button]:rounded-l-md' + : '[&:first-child[data-selected=true]_button]:rounded-l-md', + defaultClassNames.day, + ), + range_start: cn( + 'rounded-l-md bg-accent', + defaultClassNames.range_start, + ), + range_middle: cn('rounded-none', defaultClassNames.range_middle), + range_end: cn('rounded-r-md bg-accent', defaultClassNames.range_end), + today: cn( + 'rounded-md bg-accent text-accent-foreground data-[selected=true]:rounded-none', + defaultClassNames.today, + ), + outside: cn( + 'text-muted-foreground aria-selected:text-muted-foreground', + defaultClassNames.outside, + ), + disabled: cn( + 'text-muted-foreground opacity-50', + defaultClassNames.disabled, + ), + hidden: cn('invisible', defaultClassNames.hidden), + ...classNames, + }} + components={{ + Root: ({ className, rootRef, ...props }) => { + return ( +
+ ) + }, + Chevron: ({ className, orientation, ...props }) => { + if (orientation === 'left') { + return ( + + ) + } + + if (orientation === 'right') { + return ( + + ) + } + + return ( + + ) + }, + DayButton: CalendarDayButton, + WeekNumber: ({ children, ...props }) => { + return ( + +
+ {children} +
+ + ) + }, + ...components, + }} + {...props} + /> + ) +} + +function CalendarDayButton({ + className, + day, + modifiers, + ...props +}: React.ComponentProps) { + const defaultClassNames = getDefaultClassNames() + + const ref = React.useRef(null) + React.useEffect(() => { + if (modifiers.focused) ref.current?.focus() + }, [modifiers.focused]) + + return ( + + + + + +
Page
+ + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = e.target.value ? Number(e.target.value) - 1 : 0 + table.setPageIndex(page) + }} + className="page-size-input" + /> + + { - table.setPageSize(Number(e.target.value)) - }} - > - {[10, 20, 30, 40, 50].map(pageSize => ( - - ))} - -
-
-
- {Object.keys(rowSelection).length} of{' '} - {table.getPreFilteredRowModel().rows.length} Total Rows Selected -
-
-
-
- -
-
- -
-
- + > + {[10, 20, 30, 40, 50].map((pageSize) => ( + + ))} + +
+
+
+ <> + {Object.keys(table.state.rowSelection).length.toLocaleString()}{' '} + of{' '} + + {table.getPreFilteredRowModel().rows.length.toLocaleString()} Total + Rows Selected +
+
+
+
+ +
+
+ +
+
+ +
{JSON.stringify(table.state, null, 2)}
+
-
- -
{JSON.stringify(table.getState().rowSelection, null, 2)}
-
-
+ ) } @@ -298,41 +310,72 @@ function Filter({ column, table, }: { - column: Column - table: Table + column: Column + table: Table }) { const firstValue = table .getPreFilteredRowModel() .flatRows[0]?.getValue(column.id) return typeof firstValue === 'number' ? ( -
- + - column.setFilterValue((old: any) => [e.target.value, old?.[1]]) + onChange={(value) => + column.setFilterValue((old: any) => [value, old?.[1]]) } placeholder={`Min`} - className="w-24 border shadow rounded" + className="filter-input" /> - - column.setFilterValue((old: any) => [old?.[0], e.target.value]) + onChange={(value) => + column.setFilterValue((old: any) => [old?.[0], value]) } placeholder={`Max`} - className="w-24 border shadow rounded" + className="filter-input" />
) : ( - column.setFilterValue(e.target.value)} + onChange={(value) => column.setFilterValue(value)} placeholder={`Search...`} - className="w-36 border shadow rounded" + className="filter-select" + /> + ) +} + +// A debounced input react component +function DebouncedInput({ + value: initialValue, + onChange, + debounce = 500, + ...props +}: { + value: string | number + onChange: (value: string | number) => void + debounce?: number +} & Omit, 'onChange'>) { + const [value, setValue] = React.useState(initialValue) + + React.useEffect(() => { + setValue(initialValue) + }, [initialValue]) + + const debouncedOnChange = useDebouncedCallback(onChange, { wait: debounce }) + + return ( + { + setValue(e.target.value) + debouncedOnChange(e.target.value) + }} /> ) } @@ -354,7 +397,7 @@ function IndeterminateCheckbox({ ) @@ -366,5 +409,6 @@ if (!rootElement) throw new Error('Failed to find the root element') ReactDOM.createRoot(rootElement).render( - + + , ) diff --git a/examples/react/row-selection/src/makeData.ts b/examples/react/row-selection/src/makeData.ts index 331dd1eb19..c34c43a03e 100644 --- a/examples/react/row-selection/src/makeData.ts +++ b/examples/react/row-selection/src/makeData.ts @@ -1,17 +1,18 @@ import { faker } from '@faker-js/faker' export type Person = { + id: string firstName: string lastName: string age: number visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -20,6 +21,7 @@ const range = (len: number) => { const newPerson = (): Person => { return { + id: faker.string.uuid(), firstName: faker.person.firstName(), lastName: faker.person.lastName(), age: faker.number.int(40), @@ -29,14 +31,14 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! - return range(len).map((d): Person => { +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { return { ...newPerson(), subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, diff --git a/examples/react/row-selection/src/vite-env.d.ts b/examples/react/row-selection/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/row-selection/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/row-selection/tsconfig.json b/examples/react/row-selection/tsconfig.json index 6d545f543f..33a3e872be 100644 --- a/examples/react/row-selection/tsconfig.json +++ b/examples/react/row-selection/tsconfig.json @@ -5,20 +5,17 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, - "include": ["src"] + "include": ["src", "vite.config.js", "vite.config.ts"] } diff --git a/examples/react/row-selection/vite.config.js b/examples/react/row-selection/vite.config.js index 2e1361723a..1755f6bea8 100644 --- a/examples/react/row-selection/vite.config.js +++ b/examples/react/row-selection/vite.config.js @@ -1,5 +1,6 @@ import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' import rollupReplace from '@rollup/plugin-replace' // https://vitejs.dev/config/ @@ -13,5 +14,10 @@ export default defineConfig({ }, }), react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), ], }) diff --git a/examples/react/sorting/index.html b/examples/react/sorting/index.html index 3fc40c9367..1551290f72 100644 --- a/examples/react/sorting/index.html +++ b/examples/react/sorting/index.html @@ -4,7 +4,7 @@ Vite App - +
diff --git a/examples/react/sorting/package.json b/examples/react/sorting/package.json index 1d7044effa..61983abf9a 100644 --- a/examples/react/sorting/package.json +++ b/examples/react/sorting/package.json @@ -1,25 +1,28 @@ { - "name": "tanstack-table-example-sorting", - "version": "0.0.0", + "name": "tanstack-react-table-example-sorting", "private": true, "scripts": { "dev": "vite", "build": "vite build", "serve": "vite preview", - "start": "vite" + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" }, "dependencies": { - "@faker-js/faker": "^8.4.1", - "@tanstack/react-table": "^8.20.6", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "@faker-js/faker": "^10.4.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6" }, "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/react/sorting/src/index.css b/examples/react/sorting/src/index.css index 43c09e0f6b..b9af6ded32 100644 --- a/examples/react/sorting/src/index.css +++ b/examples/react/sorting/src/index.css @@ -5,6 +5,8 @@ html { table { border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; } tbody { @@ -24,3 +26,340 @@ tfoot { tfoot th { font-weight: normal; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/sorting/src/main.tsx b/examples/react/sorting/src/main.tsx index 3a5ba39de4..6571f7f28b 100644 --- a/examples/react/sorting/src/main.tsx +++ b/examples/react/sorting/src/main.tsx @@ -1,21 +1,26 @@ import React from 'react' import ReactDOM from 'react-dom/client' - import './index.css' - import { - ColumnDef, - flexRender, - getCoreRowModel, - getSortedRowModel, - SortingFn, - SortingState, - useReactTable, + createColumnHelper, + createSortedRowModel, + rowSortingFeature, + sortFns, + tableFeatures, + useTable, } from '@tanstack/react-table' -import { makeData, Person } from './makeData' +import { makeData } from './makeData' +import type { SortFn } from '@tanstack/react-table' +import type { Person } from './makeData' + +const _features = tableFeatures({ + rowSortingFeature, +}) -//custom sorting logic for one of our enum columns -const sortStatusFn: SortingFn = (rowA, rowB, _columnId) => { +const columnHelper = createColumnHelper() + +// custom sorting logic for one of our enum columns +const sortStatusFn: SortFn = (rowA, rowB, _columnId) => { const statusA = rowA.original.status const statusB = rowB.original.status const statusOrder = ['single', 'complicated', 'relationship'] @@ -25,101 +30,90 @@ const sortStatusFn: SortingFn = (rowA, rowB, _columnId) => { function App() { const rerender = React.useReducer(() => ({}), {})[1] - const [sorting, setSorting] = React.useState([]) - - const columns = React.useMemo[]>( - () => [ - { - accessorKey: 'firstName', - cell: info => info.getValue(), - //this column will sort in ascending order by default since it is a string column - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - cell: info => info.getValue(), - header: () => Last Name, - sortUndefined: 'last', //force undefined values to the end - sortDescFirst: false, //first sort order will be ascending (nullable values can mess up auto detection of sort order) - }, - { - accessorKey: 'age', - header: () => 'Age', - //this column will sort in descending order by default since it is a number column - }, - { - accessorKey: 'visits', - header: () => Visits, - sortUndefined: 'last', //force undefined values to the end - }, - { - accessorKey: 'status', - header: 'Status', - sortingFn: sortStatusFn, //use our custom sorting function for this enum column - }, - { - accessorKey: 'progress', - header: 'Profile Progress', - // enableSorting: false, //disable sorting for this column - }, - { - accessorKey: 'rank', - header: 'Rank', - invertSorting: true, //invert the sorting order (golf score-like where smaller is better) - }, - { - accessorKey: 'createdAt', - header: 'Created At', - // sortingFn: 'datetime' //make sure table knows this is a datetime column (usually can detect if no null values) - }, - ], - [] + const columns = React.useMemo( + () => + columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + // this column will sort in ascending order by default since it is a string column + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + sortUndefined: 'last', // force undefined values to the end + sortDescFirst: false, // first sort order will be ascending (nullable values can mess up auto detection of sort order) + }), + columnHelper.accessor('age', { + header: () => 'Age', + // this column will sort in descending order by default since it is a number column + }), + columnHelper.accessor('visits', { + header: () => Visits, + sortUndefined: 'last', // force undefined values to the end + }), + columnHelper.accessor('status', { + header: 'Status', + sortFn: sortStatusFn, // use our custom sorting function for this enum column + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + // enableSorting: false, //disable sorting for this column + }), + columnHelper.accessor('rank', { + header: 'Rank', + invertSorting: true, // invert the sorting order (golf score-like where smaller is better) + }), + columnHelper.accessor('createdAt', { + header: 'Created At', + // sortFn: 'datetime' //make sure table knows this is a datetime column (usually can detect if no null values) + }), + ]), + [], ) const [data, setData] = React.useState(() => makeData(1_000)) - const refreshData = () => setData(() => makeData(100_000)) //stress test with 100k rows + const refreshData = () => setData(makeData(1_000)) + const stressTest = () => setData(makeData(500_000)) - const table = useReactTable({ - columns, - data, - debugTable: true, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), //client-side sorting - onSortingChange: setSorting, //optionally control sorting state in your own scope for easy access - // sortingFns: { - // sortStatusFn, //or provide our custom sorting function globally for all columns to be able to use - // }, - //no need to pass pageCount or rowCount with client-side pagination as it is calculated automatically - state: { - sorting, + const table = useTable( + { + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), // client-side sorting + }, + columns, + data, + debugTable: true, + // no need to pass pageCount or rowCount with client-side pagination as it is calculated automatically + // autoResetPageIndex: false, // turn off page index reset when sorting or filtering - default on/true + // enableMultiSort: false, //Don't allow shift key to sort multiple columns - default on/true + // enableSorting: false, // - default on/true + // enableSortingRemoval: false, //Don't allow - default on/true + // isMultiSortEvent: (e) => true, //Make all clicks multi-sort - default requires `shift` key + // maxMultiSortColCount: 3, // only allow 3 columns to be sorted at once - default is Infinity }, - // autoResetPageIndex: false, // turn off page index reset when sorting or filtering - default on/true - // enableMultiSort: false, //Don't allow shift key to sort multiple columns - default on/true - // enableSorting: false, // - default on/true - // enableSortingRemoval: false, //Don't allow - default on/true - // isMultiSortEvent: (e) => true, //Make all clicks multi-sort - default requires `shift` key - // maxMultiSortColCount: 3, // only allow 3 columns to be sorted at once - default is Infinity - }) - - //access sorting state from the table instance - console.log(table.getState().sorting) + (state) => state, // default selector + ) return ( -
-
+
+
+ + +
+
- {table.getHeaderGroups().map(headerGroup => ( + {table.getHeaderGroups().map((headerGroup) => ( - {headerGroup.headers.map(header => { + {headerGroup.headers.map((header) => { return ( - {row.getVisibleCells().map(cell => { + {row.getAllCells().map((cell) => { return ( ) })} @@ -174,10 +162,8 @@ function App() {
-
- -
-
{JSON.stringify(sorting, null, 2)}
+ {/* Store mode: dump full table state for debugging */} +
{JSON.stringify(table.state, null, 2)}
) } @@ -187,7 +173,7 @@ const rootElement = document.getElementById('root') if (!rootElement) throw new Error('Failed to find the root element') ReactDOM.createRoot(rootElement).render( - - - + // + , + // , ) diff --git a/examples/react/sorting/src/makeData.ts b/examples/react/sorting/src/makeData.ts index d6c0639b22..fc070cd5d2 100644 --- a/examples/react/sorting/src/makeData.ts +++ b/examples/react/sorting/src/makeData.ts @@ -9,11 +9,11 @@ export type Person = { status: 'relationship' | 'complicated' | 'single' rank: number createdAt: Date - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -32,14 +32,14 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], rank: faker.number.int(100), } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] return range(len).map((_d): Person => { return { ...newPerson(), diff --git a/examples/react/sorting/src/vite-env.d.ts b/examples/react/sorting/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/sorting/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/sorting/tsconfig.json b/examples/react/sorting/tsconfig.json index 6d545f543f..33a3e872be 100644 --- a/examples/react/sorting/tsconfig.json +++ b/examples/react/sorting/tsconfig.json @@ -5,20 +5,17 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, - "include": ["src"] + "include": ["src", "vite.config.js", "vite.config.ts"] } diff --git a/examples/react/sorting/vite.config.js b/examples/react/sorting/vite.config.js index 2e1361723a..1755f6bea8 100644 --- a/examples/react/sorting/vite.config.js +++ b/examples/react/sorting/vite.config.js @@ -1,5 +1,6 @@ import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' import rollupReplace from '@rollup/plugin-replace' // https://vitejs.dev/config/ @@ -13,5 +14,10 @@ export default defineConfig({ }, }), react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), ], }) diff --git a/examples/react/sub-components/index.html b/examples/react/sub-components/index.html index 3fc40c9367..1551290f72 100644 --- a/examples/react/sub-components/index.html +++ b/examples/react/sub-components/index.html @@ -4,7 +4,7 @@ Vite App - +
diff --git a/examples/react/sub-components/package.json b/examples/react/sub-components/package.json index 4f2402df77..c8cb28e0b5 100644 --- a/examples/react/sub-components/package.json +++ b/examples/react/sub-components/package.json @@ -1,25 +1,28 @@ { - "name": "tanstack-table-example-sub-components", - "version": "0.0.0", + "name": "tanstack-react-table-example-sub-components", "private": true, "scripts": { "dev": "vite", "build": "vite build", "serve": "vite preview", - "start": "vite" + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" }, "dependencies": { - "@faker-js/faker": "^8.4.1", - "@tanstack/react-table": "^8.20.6", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "@faker-js/faker": "^10.4.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6" }, "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/react/sub-components/src/index.css b/examples/react/sub-components/src/index.css index 43c09e0f6b..b9af6ded32 100644 --- a/examples/react/sub-components/src/index.css +++ b/examples/react/sub-components/src/index.css @@ -5,6 +5,8 @@ html { table { border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; } tbody { @@ -24,3 +26,340 @@ tfoot { tfoot th { font-weight: normal; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/sub-components/src/main.tsx b/examples/react/sub-components/src/main.tsx index 4890d12c5d..07021404f0 100644 --- a/examples/react/sub-components/src/main.tsx +++ b/examples/react/sub-components/src/main.tsx @@ -1,138 +1,124 @@ import React, { Fragment } from 'react' import ReactDOM from 'react-dom/client' - import './index.css' - import { - useReactTable, - getCoreRowModel, - getExpandedRowModel, + createColumnHelper, + createExpandedRowModel, + rowExpandingFeature, + tableFeatures, + useTable, +} from '@tanstack/react-table' +import { makeData } from './makeData' +import type { ColumnDef, - flexRender, Row, + RowData, + TableFeatures, } from '@tanstack/react-table' -import { makeData, Person } from './makeData' +import type { Person } from './makeData' -const columns: ColumnDef[] = [ - { - header: 'Name', - footer: props => props.column.id, - columns: [ - { - id: 'expander', - header: () => null, - cell: ({ row }) => { - return row.getCanExpand() ? ( - - ) : ( - '🔵' - ) - }, - }, - { - accessorKey: 'firstName', - header: 'First Name', - cell: ({ row, getValue }) => ( -
- {getValue()} -
- ), - footer: props => props.column.id, - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - cell: info => info.getValue(), - header: () => Last Name, - footer: props => props.column.id, - }, - ], - }, - { - header: 'Info', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'age', - header: () => 'Age', - footer: props => props.column.id, - }, - { - header: 'More Info', - columns: [ - { - accessorKey: 'visits', - header: () => Visits, - footer: props => props.column.id, - }, - { - accessorKey: 'status', - header: 'Status', - footer: props => props.column.id, - }, - { - accessorKey: 'progress', - header: 'Profile Progress', - footer: props => props.column.id, - }, - ], - }, - ], - }, -] +const _features = tableFeatures({ + rowExpandingFeature, +}) + +const columnHelper = createColumnHelper() -type TableProps = { - data: TData[] - columns: ColumnDef[] - renderSubComponent: (props: { row: Row }) => React.ReactElement - getRowCanExpand: (row: Row) => boolean +const columns = columnHelper.columns([ + columnHelper.display({ + id: 'expander', + header: () => null, + cell: ({ row }) => { + return row.getCanExpand() ? ( + + ) : ( + '🔵' + ) + }, + }), + columnHelper.accessor('firstName', { + header: 'First Name', + cell: ({ row, getValue }) => ( +
+ {getValue()} +
+ ), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }), + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + }), + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), +]) + +type TableProps = { + data: Array + columns: Array> + renderSubComponent: (props: { + row: Row + }) => React.ReactElement + getRowCanExpand: (row: Row) => boolean } function Table({ - data, columns, - renderSubComponent, + data, getRowCanExpand, -}: TableProps): JSX.Element { - const table = useReactTable({ - data, - columns, - getRowCanExpand, - getCoreRowModel: getCoreRowModel(), - getExpandedRowModel: getExpandedRowModel(), - }) + renderSubComponent, +}: TableProps): React.JSX.Element { + const table = useTable( + { + debugTable: true, + _features, + _rowModels: { + expandedRowModel: createExpandedRowModel(), + }, + columns, + data, + getRowCanExpand, + }, + (state) => state, // default selector + ) return ( -
-
+
+
{header.isPlaceholder ? null : (
- {flexRender( - header.column.columnDef.header, - header.getContext() - )} + {{ asc: ' 🔼', desc: ' 🔽', @@ -152,16 +143,13 @@ function App() { {table .getRowModel() .rows.slice(0, 10) - .map(row => { + .map((row) => { return (
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} +
- {table.getHeaderGroups().map(headerGroup => ( + {table.getHeaderGroups().map((headerGroup) => ( - {headerGroup.headers.map(header => { + {headerGroup.headers.map((header) => { return ( @@ -142,18 +128,15 @@ function Table({ ))} - {table.getRowModel().rows.map(row => { + {table.getRowModel().rows.map((row) => { return ( {/* first row is a normal row */} - {row.getVisibleCells().map(cell => { + {row.getAllCells().map((cell) => { return ( ) })} @@ -161,7 +144,7 @@ function Table({ {row.getIsExpanded() && ( {/* 2nd row is a custom 1 cell row */} - @@ -171,13 +154,17 @@ function Table({ })}
{header.isPlaceholder ? null : (
- {flexRender( - header.column.columnDef.header, - header.getContext() - )} +
)}
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} +
+ {renderSubComponent({ row })}
-
-
{table.getRowModel().rows.length} Rows
+
+
{table.getRowModel().rows.length.toLocaleString()} Rows
) } -const renderSubComponent = ({ row }: { row: Row }) => { +const renderSubComponent = ({ + row, +}: { + row: Row +}) => { return (
       {JSON.stringify(row.original, null, 2)}
@@ -186,15 +173,27 @@ const renderSubComponent = ({ row }: { row: Row }) => {
 }
 
 function App() {
-  const [data] = React.useState(() => makeData(10))
+  const [data, setData] = React.useState(() => makeData(10))
+  const refreshData = () => setData(makeData(10))
+  const stressTest = () => setData(makeData(1_000))
 
   return (
-     true}
-      renderSubComponent={renderSubComponent}
-    />
+    
+
+ + +
+
true} + renderSubComponent={renderSubComponent} + /> + ) } @@ -204,5 +203,5 @@ if (!rootElement) throw new Error('Failed to find the root element') ReactDOM.createRoot(rootElement).render( - + , ) diff --git a/examples/react/sub-components/src/makeData.ts b/examples/react/sub-components/src/makeData.ts index 331dd1eb19..b9055b2d8c 100644 --- a/examples/react/sub-components/src/makeData.ts +++ b/examples/react/sub-components/src/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,14 +29,14 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! - return range(len).map((d): Person => { +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { return { ...newPerson(), subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, diff --git a/examples/react/sub-components/src/vite-env.d.ts b/examples/react/sub-components/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/sub-components/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/sub-components/tsconfig.json b/examples/react/sub-components/tsconfig.json index 6d545f543f..33a3e872be 100644 --- a/examples/react/sub-components/tsconfig.json +++ b/examples/react/sub-components/tsconfig.json @@ -5,20 +5,17 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, - "include": ["src"] + "include": ["src", "vite.config.js", "vite.config.ts"] } diff --git a/examples/react/sub-components/vite.config.js b/examples/react/sub-components/vite.config.js index 2e1361723a..1755f6bea8 100644 --- a/examples/react/sub-components/vite.config.js +++ b/examples/react/sub-components/vite.config.js @@ -1,5 +1,6 @@ import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' import rollupReplace from '@rollup/plugin-replace' // https://vitejs.dev/config/ @@ -13,5 +14,10 @@ export default defineConfig({ }, }), react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), ], }) diff --git a/examples/react/query-router-search-params/.gitignore b/examples/react/virtualized-columns-experimental/.gitignore similarity index 100% rename from examples/react/query-router-search-params/.gitignore rename to examples/react/virtualized-columns-experimental/.gitignore diff --git a/examples/svelte/basic/README.md b/examples/react/virtualized-columns-experimental/README.md similarity index 100% rename from examples/svelte/basic/README.md rename to examples/react/virtualized-columns-experimental/README.md diff --git a/examples/react/virtualized-columns-experimental/index.html b/examples/react/virtualized-columns-experimental/index.html new file mode 100644 index 0000000000..1551290f72 --- /dev/null +++ b/examples/react/virtualized-columns-experimental/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/react/virtualized-columns-experimental/package.json b/examples/react/virtualized-columns-experimental/package.json new file mode 100644 index 0000000000..2d4e12e788 --- /dev/null +++ b/examples/react/virtualized-columns-experimental/package.json @@ -0,0 +1,28 @@ +{ + "name": "tanstack-react-table-example-virtualized-columns-experimental", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "@tanstack/react-virtual": "^3.13.24", + "react": "^19.2.6", + "react-dom": "^19.2.6" + }, + "devDependencies": { + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/react/virtualized-columns-experimental/src/index.css b/examples/react/virtualized-columns-experimental/src/index.css new file mode 100644 index 0000000000..2160005a96 --- /dev/null +++ b/examples/react/virtualized-columns-experimental/src/index.css @@ -0,0 +1,390 @@ +:root { + --virtual-padding-left: 0px; + --virtual-padding-right: 0px; +} + +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border-collapse: collapse; + border-spacing: 0; + font-family: arial, sans-serif; + table-layout: fixed; +} + +thead { + background: lightgray; +} + +tr { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; + text-align: left; +} + +td { + padding: 6px; +} + +.container { + border: 1px solid lightgray; + margin: 1rem auto; +} + +.app { + margin: 1rem auto; + text-align: center; +} + +.left-column-spacer { + width: var(--virtual-padding-left); +} + +.right-column-spacer { + width: var(--virtual-padding-right); +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/virtualized-columns-experimental/src/main.tsx b/examples/react/virtualized-columns-experimental/src/main.tsx new file mode 100644 index 0000000000..6e1dad4615 --- /dev/null +++ b/examples/react/virtualized-columns-experimental/src/main.tsx @@ -0,0 +1,423 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import './index.css' +import { + FlexRender, + columnSizingFeature, + createColumnHelper, + createSortedRowModel, + rowSortingFeature, + sortFns, + useTable, +} from '@tanstack/react-table' +import { useVirtualizer } from '@tanstack/react-virtual' +import { makeColumns, makeData } from './makeData' +import type { + Cell, + Header, + HeaderGroup, + ReactTable, + Row, +} from '@tanstack/react-table' +import type { Virtualizer } from '@tanstack/react-virtual' +import type { Person } from './makeData' + +const _features = { + columnSizingFeature, + rowSortingFeature, +} + +const columnHelper = createColumnHelper() + +// All important CSS styles are included as inline styles for this example. This is not recommended for your code. +function App() { + const columns = React.useMemo( + () => + columnHelper.columns( + makeColumns(1_000).map((column) => + columnHelper.accessor(column.accessorKey, { + header: column.header, + size: column.size, + }), + ), + ), + [], + ) + + const [data, setData] = React.useState(() => makeData(1_000, columns)) + + const refreshData = React.useCallback(() => { + setData(makeData(1_000, columns)) + }, [columns]) + + const stressTest = React.useCallback(() => { + setData(makeData(10_000, columns)) + }, [columns]) + + // The table does not live in the same scope as the virtualizers + const table = useTable( + { + _features, + _rowModels: { sortedRowModel: createSortedRowModel(sortFns) }, + columns, + data, + debugTable: true, + }, + (state) => state, // default selector + ) + + return ( +
+ {process.env.NODE_ENV === 'development' ? ( +

+ Notice: You are currently running React in + development mode. Virtualized rendering performance will be slightly + degraded until this application is built for production. +

+ ) : null} +
({columns.length.toLocaleString()} columns)
+
({data.length.toLocaleString()} rows)
+
+ + +
+ +
+ ) +} + +interface TableContainerProps { + table: ReactTable +} + +function TableContainer({ table }: TableContainerProps) { + const visibleColumns = table.getAllLeafColumns() + + // The virtualizers need to know the scrollable container element + const tableContainerRef = React.useRef(null) + + // we are using a slightly different virtualization strategy for columns (compared to virtual rows) in order to support dynamic row heights + const columnVirtualizer = useVirtualizer< + HTMLDivElement, + HTMLTableCellElement + >({ + count: visibleColumns.length, + estimateSize: (index) => visibleColumns[index].getSize(), // estimate width of each column for accurate scrollbar dragging + getScrollElement: () => tableContainerRef.current, + horizontal: true, + overscan: 3, // how many columns to render on each side off screen each way (adjust this for performance) + onChange: (instance) => { + // requestAnimationFrame(() => { + const virtualColumns = instance.getVirtualItems() + // different virtualization strategy for columns - instead of absolute and translateY, we add empty columns to the left and right + const virtualPaddingLeft = virtualColumns[0]?.start ?? 0 + const virtualPaddingRight = + instance.getTotalSize() - + (virtualColumns[virtualColumns.length - 1]?.end ?? 0) + + tableContainerRef.current?.style.setProperty( + '--virtual-padding-left', + `${virtualPaddingLeft}px`, + ) + tableContainerRef.current?.style.setProperty( + '--virtual-padding-right', + `${virtualPaddingRight}px`, + ) + // }) + }, + }) + + return ( +
+ {/* Even though we're still using sematic table tags, we must use CSS grid and flexbox for dynamic row heights */} +
+ + +
+
+ ) +} + +interface TableHeadProps { + columnVirtualizer: Virtualizer + table: ReactTable +} + +function TableHead({ table, columnVirtualizer }: TableHeadProps) { + return ( + + {table.getHeaderGroups().map((headerGroup) => ( + + ))} + + ) +} + +interface TableHeadRowProps { + columnVirtualizer: Virtualizer + headerGroup: HeaderGroup +} + +function TableHeadRow({ columnVirtualizer, headerGroup }: TableHeadRowProps) { + const virtualColumnIndexes = columnVirtualizer.getVirtualIndexes() + + return ( + + {/* fake empty column to the left for virtualization scroll padding */} + + {virtualColumnIndexes.map((virtualColumnIndex) => { + const header = headerGroup.headers[virtualColumnIndex] + return ( + + ) + })} + {/* fake empty column to the right for virtualization scroll padding */} + + + ) +} + +interface TableHeadCellProps { + columnVirtualizer: Virtualizer + header: Header +} + +function TableHeadCell({ + columnVirtualizer: _columnVirtualizer, + header, +}: TableHeadCellProps) { + return ( + +
+ + {{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null} +
+ + ) +} + +const TableHeadCellMemo = React.memo( + TableHeadCell, + (_prev, next) => next.columnVirtualizer.isScrolling, +) as typeof TableHeadCell + +interface TableBodyProps { + columnVirtualizer: Virtualizer + table: ReactTable + tableContainerRef: React.RefObject +} + +function TableBody({ + columnVirtualizer, + table, + tableContainerRef, +}: TableBodyProps) { + const tableBodyRef = React.useRef(null) + const rowRefsMap = React.useRef>(new Map()) + + const { rows } = table.getRowModel() + + // dynamic row height virtualization - alternatively you could use a simpler fixed row height strategy without the need for `measureElement` + const rowVirtualizer = useVirtualizer({ + count: rows.length, + estimateSize: () => 33, // estimate row height for accurate scrollbar dragging + getScrollElement: () => tableContainerRef.current, + // measure dynamic row height, except in firefox because it measures table border height incorrectly + measureElement: + typeof window !== 'undefined' && + navigator.userAgent.indexOf('Firefox') === -1 + ? (element) => element.getBoundingClientRect().height + : undefined, + overscan: 5, + onChange: (instance) => { + // requestAnimationFrame(() => { + tableBodyRef.current!.style.height = `${instance.getTotalSize()}px` + instance.getVirtualItems().forEach((virtualRow) => { + const rowRef = rowRefsMap.current.get(virtualRow.index) + if (!rowRef) return + rowRef.style.transform = `translateY(${virtualRow.start}px)` + }) + // }) + }, + }) + + React.useLayoutEffect(() => { + rowVirtualizer.measure() + }, [table.store.state]) + + const virtualRowIndexes = rowVirtualizer.getVirtualIndexes() + + return ( + + {virtualRowIndexes.map((virtualRowIndex) => { + const row = rows[virtualRowIndex] + + return ( + + ) + })} + + ) +} + +interface TableBodyRowProps { + columnVirtualizer: Virtualizer + row: Row + rowVirtualizer: Virtualizer + virtualRowIndex: number + rowRefsMap: React.RefObject> +} + +function TableBodyRow({ + columnVirtualizer, + row, + rowVirtualizer, + virtualRowIndex, + rowRefsMap, +}: TableBodyRowProps) { + const visibleCells = row.getAllCells() + const virtualColumnIndexes = columnVirtualizer.getVirtualIndexes() + + return ( + { + if (node && typeof virtualRowIndex !== 'undefined') { + rowVirtualizer.measureElement(node) + rowRefsMap.current.set(virtualRowIndex, node) + } + }} // measure dynamic row height + key={row.id} + style={{ + display: 'flex', + position: 'absolute', + width: '100%', + }} + > + {/* fake empty column to the left for virtualization scroll padding */} + + {virtualColumnIndexes.map((virtualColumnIndex) => { + const cell = visibleCells[virtualColumnIndex] + return ( + + ) + })} + {/* fake empty column to the right for virtualization scroll padding */} + + + ) +} + +// TODO: Can rows be memoized in any way without breaking column virtualization? +// const TableBodyRowMemo = React.memo( +// TableBodyRow, +// (_prev, next) => next.rowVirtualizer.isScrolling +// ) + +interface TableBodyCellProps { + cell: Cell + columnVirtualizer: Virtualizer +} + +function TableBodyCell({ + cell, + columnVirtualizer: _columnVirtualizer, +}: TableBodyCellProps) { + return ( + + + + ) +} + +const TableBodyCellMemo = React.memo( + TableBodyCell, + (_prev, next) => next.columnVirtualizer.isScrolling, +) as typeof TableBodyCell + +const rootElement = document.getElementById('root') + +if (!rootElement) throw new Error('Failed to find the root element') + +ReactDOM.createRoot(rootElement).render( + + + , +) diff --git a/examples/react/virtualized-columns-experimental/src/makeData.ts b/examples/react/virtualized-columns-experimental/src/makeData.ts new file mode 100644 index 0000000000..f6d7453fd1 --- /dev/null +++ b/examples/react/virtualized-columns-experimental/src/makeData.ts @@ -0,0 +1,22 @@ +import { faker } from '@faker-js/faker' + +export type Person = Record + +export const makeColumns = (num: number) => + [...Array(num)].map((_, i) => { + return { + accessorKey: i.toString(), + header: 'Column ' + i.toString(), + size: Math.floor(Math.random() * 150) + 100, + } + }) + +export const makeData = (num: number, columns: Array): Array => + [...Array(num)].map(() => ({ + ...Object.fromEntries( + columns.map((col) => [ + (col as { accessorKey?: string }).accessorKey, + faker.person.firstName(), + ]), + ), + })) diff --git a/examples/react/virtualized-columns-experimental/src/vite-env.d.ts b/examples/react/virtualized-columns-experimental/src/vite-env.d.ts new file mode 100644 index 0000000000..d1145797cd --- /dev/null +++ b/examples/react/virtualized-columns-experimental/src/vite-env.d.ts @@ -0,0 +1,7 @@ +/// + +declare const process: { + readonly env: { + readonly NODE_ENV?: string + } +} diff --git a/examples/react/virtualized-columns-experimental/tsconfig.json b/examples/react/virtualized-columns-experimental/tsconfig.json new file mode 100644 index 0000000000..33a3e872be --- /dev/null +++ b/examples/react/virtualized-columns-experimental/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/react/virtualized-columns-experimental/vite.config.js b/examples/react/virtualized-columns-experimental/vite.config.js new file mode 100644 index 0000000000..1755f6bea8 --- /dev/null +++ b/examples/react/virtualized-columns-experimental/vite.config.js @@ -0,0 +1,23 @@ +import { defineConfig } from 'vite' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), + ], +}) diff --git a/examples/react/virtualized-columns/index.html b/examples/react/virtualized-columns/index.html index 3fc40c9367..1551290f72 100644 --- a/examples/react/virtualized-columns/index.html +++ b/examples/react/virtualized-columns/index.html @@ -4,7 +4,7 @@ Vite App - +
diff --git a/examples/react/virtualized-columns/package.json b/examples/react/virtualized-columns/package.json index 91126df109..f3683fc428 100644 --- a/examples/react/virtualized-columns/package.json +++ b/examples/react/virtualized-columns/package.json @@ -1,26 +1,29 @@ { - "name": "tanstack-table-example-virtualized-columns", - "version": "0.0.0", + "name": "tanstack-react-table-example-virtualized-columns", "private": true, "scripts": { "dev": "vite", "build": "vite build", "serve": "vite preview", - "start": "vite" + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" }, "dependencies": { - "@faker-js/faker": "^8.4.1", - "@tanstack/react-table": "^8.20.6", - "@tanstack/react-virtual": "^3.8.1", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "@faker-js/faker": "^10.4.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "@tanstack/react-virtual": "^3.13.24", + "react": "^19.2.6", + "react-dom": "^19.2.6" }, "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/react/virtualized-columns/src/index.css b/examples/react/virtualized-columns/src/index.css index 6402cb4b8b..720064ab9d 100644 --- a/examples/react/virtualized-columns/src/index.css +++ b/examples/react/virtualized-columns/src/index.css @@ -38,3 +38,340 @@ td { margin: 1rem auto; text-align: center; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/virtualized-columns/src/main.tsx b/examples/react/virtualized-columns/src/main.tsx index 982b6447db..1a2c9376f4 100644 --- a/examples/react/virtualized-columns/src/main.tsx +++ b/examples/react/virtualized-columns/src/main.tsx @@ -1,218 +1,382 @@ import React from 'react' import ReactDOM from 'react-dom/client' - import './index.css' - import { - ColumnDef, - flexRender, - getCoreRowModel, - getSortedRowModel, + columnSizingFeature, + columnVisibilityFeature, + createColumnHelper, + createSortedRowModel, + rowSortingFeature, + sortFns, + useTable, +} from '@tanstack/react-table' +import { useVirtualizer } from '@tanstack/react-virtual' +import { makeColumns, makeData } from './makeData' +import type { + Cell, + Header, + HeaderGroup, + ReactTable, Row, - useReactTable, } from '@tanstack/react-table' +import type { VirtualItem, Virtualizer } from '@tanstack/react-virtual' +import type { Person } from './makeData' -import { useVirtualizer } from '@tanstack/react-virtual' +const features = { + columnSizingFeature, + columnVisibilityFeature, + rowSortingFeature, +} -import { makeColumns, makeData, Person } from './makeData' +const columnHelper = createColumnHelper() function App() { - const columns = React.useMemo[]>( - () => makeColumns(1_000), - [] + const columns = React.useMemo( + () => + columnHelper.columns( + makeColumns(1_000).map((column) => + columnHelper.accessor(column.accessorKey, { + header: column.header, + size: column.size, + }), + ), + ), + [], ) - const [data, _setData] = React.useState(() => makeData(1_000, columns)) + const [data, setData] = React.useState(() => makeData(1_000, columns)) - const table = useReactTable({ - data, - columns, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), - debugTable: true, - }) + const refreshData = React.useCallback(() => { + setData(makeData(1_000, columns)) + }, [columns]) - const { rows } = table.getRowModel() + const stressTest = React.useCallback(() => { + setData(makeData(10_000, columns)) + }, [columns]) + + const table = useTable( + { + _features: features, + _rowModels: { sortedRowModel: createSortedRowModel(sortFns) }, + columns, + data, + debugTable: true, + }, + (state) => state, // default selector + ) + + // All important CSS styles are included as inline styles for this example. This is not recommended for your code. + return ( +
+ {process.env.NODE_ENV === 'development' ? ( +

+ Notice: You are currently running React in + development mode. Virtualized rendering performance will be slightly + degraded until this application is built for production. +

+ ) : null} +
({columns.length.toLocaleString()} columns)
+
({data.length.toLocaleString()} rows)
+
+ + +
+ +
+ ) +} +interface TableContainerProps { + table: ReactTable +} + +function TableContainer({ table }: TableContainerProps) { const visibleColumns = table.getVisibleLeafColumns() - //The virtualizers need to know the scrollable container element + // The virtualizers need to know the scrollable container element const tableContainerRef = React.useRef(null) - //we are using a slightly different virtualization strategy for columns (compared to virtual rows) in order to support dynamic row heights - const columnVirtualizer = useVirtualizer({ + // we are using a slightly different virtualization strategy for columns (compared to virtual rows) in order to support dynamic row heights + const columnVirtualizer = useVirtualizer< + HTMLDivElement, + HTMLTableCellElement + >({ count: visibleColumns.length, - estimateSize: index => visibleColumns[index].getSize(), //estimate width of each column for accurate scrollbar dragging + estimateSize: (index) => visibleColumns[index].getSize(), // estimate width of each column for accurate scrollbar dragging getScrollElement: () => tableContainerRef.current, horizontal: true, - overscan: 3, //how many columns to render on each side off screen each way (adjust this for performance) - }) - - //dynamic row height virtualization - alternatively you could use a simpler fixed row height strategy without the need for `measureElement` - const rowVirtualizer = useVirtualizer({ - count: rows.length, - estimateSize: () => 33, //estimate row height for accurate scrollbar dragging - getScrollElement: () => tableContainerRef.current, - //measure dynamic row height, except in firefox because it measures table border height incorrectly - measureElement: - typeof window !== 'undefined' && - navigator.userAgent.indexOf('Firefox') === -1 - ? element => element?.getBoundingClientRect().height - : undefined, - overscan: 5, + overscan: 3, // how many columns to render on each side off screen each way (adjust this for performance) }) const virtualColumns = columnVirtualizer.getVirtualItems() - const virtualRows = rowVirtualizer.getVirtualItems() - //different virtualization strategy for columns - instead of absolute and translateY, we add empty columns to the left and right + // different virtualization strategy for columns - instead of absolute and translateY, we add empty columns to the left and right let virtualPaddingLeft: number | undefined let virtualPaddingRight: number | undefined - if (columnVirtualizer && virtualColumns?.length) { + if (virtualColumns.length) { virtualPaddingLeft = virtualColumns[0]?.start ?? 0 virtualPaddingRight = columnVirtualizer.getTotalSize() - (virtualColumns[virtualColumns.length - 1]?.end ?? 0) } - //All important CSS styles are included as inline styles for this example. This is not recommended for your code. return ( -
- {process.env.NODE_ENV === 'development' ? ( -

- Notice: You are currently running React in - development mode. Virtualized rendering performance will be slightly - degraded until this application is built for production. -

+
+ {/* Even though we're still using sematic table tags, we must use CSS grid and flexbox for dynamic row heights */} + + + +
+
+ ) +} + +interface TableHeadProps { + columnVirtualizer: Virtualizer + table: ReactTable + virtualPaddingLeft: number | undefined + virtualPaddingRight: number | undefined +} + +function TableHead({ + columnVirtualizer, + table, + virtualPaddingLeft, + virtualPaddingRight, +}: TableHeadProps) { + return ( + + {table.getHeaderGroups().map((headerGroup) => ( + + ))} + + ) +} + +interface TableHeadRowProps { + columnVirtualizer: Virtualizer + headerGroup: HeaderGroup + virtualPaddingLeft: number | undefined + virtualPaddingRight: number | undefined + table: ReactTable +} + +function TableHeadRow({ + columnVirtualizer, + headerGroup, + virtualPaddingLeft, + virtualPaddingRight, + table, +}: TableHeadRowProps) { + const virtualColumns = columnVirtualizer.getVirtualItems() + return ( + + {virtualPaddingLeft ? ( + // fake empty column to the left for virtualization scroll padding + ) : null} -
({columns.length.toLocaleString()} columns)
-
({data.length.toLocaleString()} rows)
+ {virtualColumns.map((virtualColumn) => { + const header = headerGroup.headers[virtualColumn.index] + return + })} + {virtualPaddingRight ? ( + // fake empty column to the right for virtualization scroll padding + + ) : null} + + ) +} + +interface TableHeadCellProps { + header: Header + table: ReactTable +} +function TableHeadCell({ header, table }: TableHeadCellProps) { + return ( +
- {/* Even though we're still using sematic table tags, we must use CSS grid and flexbox for dynamic row heights */} - - - {table.getHeaderGroups().map(headerGroup => ( - - {virtualPaddingLeft ? ( - //fake empty column to the left for virtualization scroll padding - - ) - })} - {virtualPaddingRight ? ( - //fake empty column to the right for virtualization scroll padding - - ))} - - - {virtualRows.map(virtualRow => { - const row = rows[virtualRow.index] as Row - const visibleCells = row.getVisibleCells() - - return ( - rowVirtualizer.measureElement(node)} //measure dynamic row height - key={row.id} - style={{ - display: 'flex', - position: 'absolute', - transform: `translateY(${virtualRow.start}px)`, //this should always be a `style` as it changes on scroll - width: '100%', - }} - > - {virtualPaddingLeft ? ( - //fake empty column to the left for virtualization scroll padding - - ) - })} - {virtualPaddingRight ? ( - //fake empty column to the right for virtualization scroll padding - - ) - })} - -
- ) : null} - {virtualColumns.map(vc => { - const header = headerGroup.headers[vc.index] - return ( - -
- {flexRender( - header.column.columnDef.header, - header.getContext() - )} - {{ - asc: ' 🔼', - desc: ' 🔽', - }[header.column.getIsSorted() as string] ?? null} -
-
- ) : null} -
- ) : null} - {virtualColumns.map(vc => { - const cell = visibleCells[vc.index] - return ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - - ) : null} -
+ + {{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null}
-
+ + ) +} + +interface TableBodyProps { + columnVirtualizer: Virtualizer + table: ReactTable + tableContainerRef: React.RefObject + virtualPaddingLeft: number | undefined + virtualPaddingRight: number | undefined +} + +function TableBody({ + columnVirtualizer, + table, + tableContainerRef, + virtualPaddingLeft, + virtualPaddingRight, +}: TableBodyProps) { + const { rows } = table.getRowModel() + + // dynamic row height virtualization - alternatively you could use a simpler fixed row height strategy without the need for `measureElement` + const rowVirtualizer = useVirtualizer({ + count: rows.length, + estimateSize: () => 33, // estimate row height for accurate scrollbar dragging + getScrollElement: () => tableContainerRef.current, + // measure dynamic row height, except in firefox because it measures table border height incorrectly + measureElement: + typeof window !== 'undefined' && + navigator.userAgent.indexOf('Firefox') === -1 + ? (element) => element.getBoundingClientRect().height + : undefined, + overscan: 5, + }) + + const virtualRows = rowVirtualizer.getVirtualItems() + + return ( + + {virtualRows.map((virtualRow) => { + const row = rows[virtualRow.index] + + return ( + + ) + })} + + ) +} + +interface TableBodyRowProps { + columnVirtualizer: Virtualizer + row: Row + rowVirtualizer: Virtualizer + virtualPaddingLeft: number | undefined + virtualPaddingRight: number | undefined + virtualRow: VirtualItem + table: ReactTable +} + +function TableBodyRow({ + columnVirtualizer, + row, + rowVirtualizer, + virtualPaddingLeft, + virtualPaddingRight, + virtualRow, + table, +}: TableBodyRowProps) { + const visibleCells = row.getVisibleCells() + const virtualColumns = columnVirtualizer.getVirtualItems() + return ( + rowVirtualizer.measureElement(node)} // measure dynamic row height + key={row.id} + style={{ + display: 'flex', + position: 'absolute', + transform: `translateY(${virtualRow.start}px)`, // this should always be a `style` as it changes on scroll + width: '100%', + }} + > + {virtualPaddingLeft ? ( + // fake empty column to the left for virtualization scroll padding + + ) : null} + {virtualColumns.map((vc) => { + const cell = visibleCells[vc.index] + return + })} + {virtualPaddingRight ? ( + // fake empty column to the right for virtualization scroll padding + + ) : null} + + ) +} + +interface TableBodyCellProps { + cell: Cell + table: ReactTable +} + +function TableBodyCell({ cell, table }: TableBodyCellProps) { + return ( + + + ) } @@ -223,5 +387,5 @@ if (!rootElement) throw new Error('Failed to find the root element') ReactDOM.createRoot(rootElement).render( - + , ) diff --git a/examples/react/virtualized-columns/src/makeData.ts b/examples/react/virtualized-columns/src/makeData.ts index 3fde072d12..2b3277cf86 100644 --- a/examples/react/virtualized-columns/src/makeData.ts +++ b/examples/react/virtualized-columns/src/makeData.ts @@ -1,6 +1,6 @@ import { faker } from '@faker-js/faker' -export const makeColumns = num => +export const makeColumns = (num: number) => [...Array(num)].map((_, i) => { return { accessorKey: i.toString(), @@ -9,10 +9,10 @@ export const makeColumns = num => } }) -export const makeData = (num, columns) => +export const makeData = (num: number, columns: Array) => [...Array(num)].map(() => ({ ...Object.fromEntries( - columns.map(col => [col.accessorKey, faker.person.firstName()]) + columns.map((col: any) => [col.accessorKey, faker.person.firstName()]), ), })) diff --git a/examples/react/virtualized-columns/src/vite-env.d.ts b/examples/react/virtualized-columns/src/vite-env.d.ts new file mode 100644 index 0000000000..d1145797cd --- /dev/null +++ b/examples/react/virtualized-columns/src/vite-env.d.ts @@ -0,0 +1,7 @@ +/// + +declare const process: { + readonly env: { + readonly NODE_ENV?: string + } +} diff --git a/examples/react/virtualized-columns/tsconfig.json b/examples/react/virtualized-columns/tsconfig.json index 6d545f543f..33a3e872be 100644 --- a/examples/react/virtualized-columns/tsconfig.json +++ b/examples/react/virtualized-columns/tsconfig.json @@ -5,20 +5,17 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, - "include": ["src"] + "include": ["src", "vite.config.js", "vite.config.ts"] } diff --git a/examples/react/virtualized-columns/vite.config.js b/examples/react/virtualized-columns/vite.config.js index 2e1361723a..1755f6bea8 100644 --- a/examples/react/virtualized-columns/vite.config.js +++ b/examples/react/virtualized-columns/vite.config.js @@ -1,5 +1,6 @@ import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' import rollupReplace from '@rollup/plugin-replace' // https://vitejs.dev/config/ @@ -13,5 +14,10 @@ export default defineConfig({ }, }), react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), ], }) diff --git a/examples/react/virtualized-infinite-scrolling/index.html b/examples/react/virtualized-infinite-scrolling/index.html index 3fc40c9367..1551290f72 100644 --- a/examples/react/virtualized-infinite-scrolling/index.html +++ b/examples/react/virtualized-infinite-scrolling/index.html @@ -4,7 +4,7 @@ Vite App - +
diff --git a/examples/react/virtualized-infinite-scrolling/package.json b/examples/react/virtualized-infinite-scrolling/package.json index 7250cfe972..198d6ca94c 100644 --- a/examples/react/virtualized-infinite-scrolling/package.json +++ b/examples/react/virtualized-infinite-scrolling/package.json @@ -1,27 +1,30 @@ { - "name": "tanstack-table-example-virtualized-infinite-scrolling", - "version": "0.0.0", + "name": "tanstack-react-table-example-virtualized-infinite-scrolling", "private": true, "scripts": { "dev": "vite", "build": "vite build", "serve": "vite preview", - "start": "vite" + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" }, "dependencies": { - "@faker-js/faker": "^8.4.1", - "@tanstack/react-query": "^5.49.0", - "@tanstack/react-table": "^8.20.6", - "@tanstack/react-virtual": "^3.8.1", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "@faker-js/faker": "^10.4.0", + "@tanstack/react-query": "^5.100.9", + "@tanstack/react-table": "^9.0.0-alpha.45", + "@tanstack/react-virtual": "^3.13.24", + "react": "^19.2.6", + "react-dom": "^19.2.6" }, "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/react/virtualized-infinite-scrolling/src/index.css b/examples/react/virtualized-infinite-scrolling/src/index.css index 6402cb4b8b..720064ab9d 100644 --- a/examples/react/virtualized-infinite-scrolling/src/index.css +++ b/examples/react/virtualized-infinite-scrolling/src/index.css @@ -38,3 +38,340 @@ td { margin: 1rem auto; text-align: center; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/virtualized-infinite-scrolling/src/main.tsx b/examples/react/virtualized-infinite-scrolling/src/main.tsx index 2042e8e38e..66e579cc27 100644 --- a/examples/react/virtualized-infinite-scrolling/src/main.tsx +++ b/examples/react/virtualized-infinite-scrolling/src/main.tsx @@ -1,93 +1,89 @@ import React from 'react' import ReactDOM from 'react-dom/client' - import './index.css' -//3 TanStack Libraries!!! +// 3 TanStack Libraries!!! import { - ColumnDef, - flexRender, - getCoreRowModel, - getSortedRowModel, - OnChangeFn, - Row, - SortingState, - useReactTable, + columnSizingFeature, + createColumnHelper, + createSortedRowModel, + rowSortingFeature, + sortFns, + tableFeatures, + useTable, } from '@tanstack/react-table' import { - keepPreviousData, QueryClient, QueryClientProvider, + keepPreviousData, useInfiniteQuery, } from '@tanstack/react-query' import { useVirtualizer } from '@tanstack/react-virtual' - -import { fetchData, Person, PersonApiResponse } from './makeData' +import { fetchData } from './makeData' +import type { Person, PersonApiResponse } from './makeData' +import type { OnChangeFn, SortingState } from '@tanstack/react-table' const fetchSize = 50 +const _features = tableFeatures({ columnSizingFeature, rowSortingFeature }) + +const columnHelper = createColumnHelper() + function App() { - //we need a reference to the scrolling element for logic down below + // we need a reference to the scrolling element for logic down below const tableContainerRef = React.useRef(null) const [sorting, setSorting] = React.useState([]) - const columns = React.useMemo[]>( - () => [ - { - accessorKey: 'id', - header: 'ID', - size: 60, - }, - { - accessorKey: 'firstName', - cell: info => info.getValue(), - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - cell: info => info.getValue(), - header: () => Last Name, - }, - { - accessorKey: 'age', - header: () => 'Age', - size: 50, - }, - { - accessorKey: 'visits', - header: () => Visits, - size: 50, - }, - { - accessorKey: 'status', - header: 'Status', - }, - { - accessorKey: 'progress', - header: 'Profile Progress', - size: 80, - }, - { - accessorKey: 'createdAt', - header: 'Created At', - cell: info => info.getValue().toLocaleString(), - size: 200, - }, - ], - [] + const columns = React.useMemo( + () => + columnHelper.columns([ + columnHelper.accessor('id', { + header: 'ID', + size: 60, + }), + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + }), + columnHelper.accessor('age', { + header: () => 'Age', + size: 50, + }), + columnHelper.accessor('visits', { + header: () => Visits, + size: 50, + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + size: 80, + }), + columnHelper.accessor('createdAt', { + header: 'Created At', + cell: (info) => info.getValue().toLocaleString(), + size: 200, + }), + ]), + [], ) - //react-query has a useInfiniteQuery hook that is perfect for this use case + // react-query has a useInfiniteQuery hook that is perfect for this use case const { data, fetchNextPage, isFetching, isLoading } = useInfiniteQuery({ queryKey: [ 'people', - sorting, //refetch when sorting changes + sorting, // refetch when sorting changes ], queryFn: async ({ pageParam = 0 }) => { const start = (pageParam as number) * fetchSize - const fetchedData = await fetchData(start, fetchSize, sorting) //pretend api call + const fetchedData = await fetchData(start, fetchSize, sorting) // pretend api call return fetchedData }, initialPageParam: 0, @@ -96,20 +92,20 @@ function App() { placeholderData: keepPreviousData, }) - //flatten the array of arrays from the useInfiniteQuery hook + // flatten the array of arrays from the useInfiniteQuery hook const flatData = React.useMemo( - () => data?.pages?.flatMap(page => page.data) ?? [], - [data] + () => data?.pages.flatMap((page) => page.data) ?? [], + [data], ) - const totalDBRowCount = data?.pages?.[0]?.meta?.totalRowCount ?? 0 + const totalDBRowCount = data?.pages[0]?.meta?.totalRowCount ?? 0 const totalFetched = flatData.length - //called on scroll and possibly on mount to fetch more data as the user scrolls and reaches bottom of table + // called on scroll and possibly on mount to fetch more data as the user scrolls and reaches bottom of table const fetchMoreOnBottomReached = React.useCallback( (containerRefElement?: HTMLDivElement | null) => { if (containerRefElement) { const { scrollHeight, scrollTop, clientHeight } = containerRefElement - //once the user has scrolled within 500px of the bottom of the table, fetch more data if we can + // once the user has scrolled within 500px of the bottom of the table, fetch more data if we can if ( scrollHeight - scrollTop - clientHeight < 500 && !isFetching && @@ -119,36 +115,39 @@ function App() { } } }, - [fetchNextPage, isFetching, totalFetched, totalDBRowCount] + [fetchNextPage, isFetching, totalFetched, totalDBRowCount], ) - //a check on mount and after a fetch to see if the table is already scrolled to the bottom and immediately needs to fetch more data + // a check on mount and after a fetch to see if the table is already scrolled to the bottom and immediately needs to fetch more data React.useEffect(() => { fetchMoreOnBottomReached(tableContainerRef.current) }, [fetchMoreOnBottomReached]) - const table = useReactTable({ - data: flatData, - columns, - state: { - sorting, + const table = useTable( + { + _features, + _rowModels: { sortedRowModel: createSortedRowModel(sortFns) }, + data: flatData, + columns, + state: { + sorting, + }, + manualSorting: true, + debugTable: true, }, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), - manualSorting: true, - debugTable: true, - }) + (state) => state, // default selector + ) - //scroll to top of table when sorting changes - const handleSortingChange: OnChangeFn = updater => { + // scroll to top of table when sorting changes + const handleSortingChange: OnChangeFn = (updater) => { setSorting(updater) - if (!!table.getRowModel().rows.length) { - rowVirtualizer.scrollToIndex?.(0) + if (table.getRowModel().rows.length) { + rowVirtualizer.scrollToIndex(0) } } - //since this table option is derived from table row model state, we're using the table.setOptions utility - table.setOptions(prev => ({ + // since this table option is derived from table row model state, we're using the table.setOptions utility + table.setOptions((prev) => ({ ...prev, onSortingChange: handleSortingChange, })) @@ -157,13 +156,13 @@ function App() { const rowVirtualizer = useVirtualizer({ count: rows.length, - estimateSize: () => 33, //estimate row height for accurate scrollbar dragging + estimateSize: () => 33, // estimate row height for accurate scrollbar dragging getScrollElement: () => tableContainerRef.current, - //measure dynamic row height, except in firefox because it measures table border height incorrectly + // measure dynamic row height, except in firefox because it measures table border height incorrectly measureElement: typeof window !== 'undefined' && navigator.userAgent.indexOf('Firefox') === -1 - ? element => element?.getBoundingClientRect().height + ? (element) => element.getBoundingClientRect().height : undefined, overscan: 5, }) @@ -181,15 +180,16 @@ function App() { degraded until this application is built for production.

) : null} - ({flatData.length} of {totalDBRowCount} rows fetched) + ({flatData.length.toLocaleString()} of {totalDBRowCount.toLocaleString()}{' '} + rows fetched)
fetchMoreOnBottomReached(e.currentTarget)} + onScroll={(e) => fetchMoreOnBottomReached(e.currentTarget)} ref={tableContainerRef} style={{ - overflow: 'auto', //our scrollable table container - position: 'relative', //needed for sticky header - height: '600px', //should be a fixed height + overflow: 'auto', // our scrollable table container + position: 'relative', // needed for sticky header + height: '600px', // should be a fixed height }} > {/* Even though we're still using sematic table tags, we must use CSS grid and flexbox for dynamic row heights */} @@ -202,12 +202,12 @@ function App() { zIndex: 1, }} > - {table.getHeaderGroups().map(headerGroup => ( + {table.getHeaderGroups().map((headerGroup) => ( - {headerGroup.headers.map(header => { + {headerGroup.headers.map((header) => { return (
- {flexRender( - header.column.columnDef.header, - header.getContext() - )} + {{ asc: ' 🔼', desc: ' 🔽', @@ -242,25 +237,25 @@ function App() { - {rowVirtualizer.getVirtualItems().map(virtualRow => { - const row = rows[virtualRow.index] as Row + {rowVirtualizer.getVirtualItems().map((virtualRow) => { + const row = rows[virtualRow.index] return ( rowVirtualizer.measureElement(node)} //measure dynamic row height + data-index={virtualRow.index} // needed for dynamic row height measurement + ref={(node) => rowVirtualizer.measureElement(node)} // measure dynamic row height key={row.id} style={{ display: 'flex', position: 'absolute', - transform: `translateY(${virtualRow.start}px)`, //this should always be a `style` as it changes on scroll + transform: `translateY(${virtualRow.start}px)`, // this should always be a `style` as it changes on scroll width: '100%', }} > - {row.getVisibleCells().map(cell => { + {row.getAllCells().map((cell) => { return ( - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} + ) })} @@ -298,5 +290,5 @@ ReactDOM.createRoot(rootElement).render( - + , ) diff --git a/examples/react/virtualized-infinite-scrolling/src/makeData.ts b/examples/react/virtualized-infinite-scrolling/src/makeData.ts index 57ed8a1071..ee8bd2efdb 100644 --- a/examples/react/virtualized-infinite-scrolling/src/makeData.ts +++ b/examples/react/virtualized-infinite-scrolling/src/makeData.ts @@ -1,5 +1,5 @@ import { faker } from '@faker-js/faker' -import { ColumnSort, SortingState } from '@tanstack/react-table' +import type { SortingState } from '@tanstack/react-table' export type Person = { id: number @@ -13,14 +13,14 @@ export type Person = { } export type PersonApiResponse = { - data: Person[] + data: Array meta: { totalRowCount: number } } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -40,13 +40,13 @@ const newPerson = (index: number): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] return range(len).map((d): Person => { return { ...newPerson(d), @@ -59,15 +59,15 @@ export function makeData(...lens: number[]) { const data = makeData(1000) -//simulates a backend api +// simulates a backend api export const fetchData = async ( start: number, size: number, - sorting: SortingState + sorting: SortingState, ) => { const dbData = [...data] if (sorting.length) { - const sort = sorting[0] as ColumnSort + const sort = sorting[0] const { id, desc } = sort as { id: keyof Person; desc: boolean } dbData.sort((a, b) => { if (desc) { @@ -77,8 +77,8 @@ export const fetchData = async ( }) } - //simulate a backend api - await new Promise(resolve => setTimeout(resolve, 200)) + // simulate a backend api + await new Promise((resolve) => setTimeout(resolve, 200)) return { data: dbData.slice(start, start + size), diff --git a/examples/react/virtualized-infinite-scrolling/src/vite-env.d.ts b/examples/react/virtualized-infinite-scrolling/src/vite-env.d.ts new file mode 100644 index 0000000000..d1145797cd --- /dev/null +++ b/examples/react/virtualized-infinite-scrolling/src/vite-env.d.ts @@ -0,0 +1,7 @@ +/// + +declare const process: { + readonly env: { + readonly NODE_ENV?: string + } +} diff --git a/examples/react/virtualized-infinite-scrolling/tsconfig.json b/examples/react/virtualized-infinite-scrolling/tsconfig.json index 6d545f543f..33a3e872be 100644 --- a/examples/react/virtualized-infinite-scrolling/tsconfig.json +++ b/examples/react/virtualized-infinite-scrolling/tsconfig.json @@ -5,20 +5,17 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, - "include": ["src"] + "include": ["src", "vite.config.js", "vite.config.ts"] } diff --git a/examples/react/virtualized-infinite-scrolling/vite.config.js b/examples/react/virtualized-infinite-scrolling/vite.config.js index 2e1361723a..1755f6bea8 100644 --- a/examples/react/virtualized-infinite-scrolling/vite.config.js +++ b/examples/react/virtualized-infinite-scrolling/vite.config.js @@ -1,5 +1,6 @@ import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' import rollupReplace from '@rollup/plugin-replace' // https://vitejs.dev/config/ @@ -13,5 +14,10 @@ export default defineConfig({ }, }), react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), ], }) diff --git a/examples/solid/bootstrap/.gitignore b/examples/react/virtualized-rows-experimental/.gitignore similarity index 100% rename from examples/solid/bootstrap/.gitignore rename to examples/react/virtualized-rows-experimental/.gitignore diff --git a/examples/react/virtualized-rows-experimental/README.md b/examples/react/virtualized-rows-experimental/README.md new file mode 100644 index 0000000000..b168d3c4b1 --- /dev/null +++ b/examples/react/virtualized-rows-experimental/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run start` or `yarn start` diff --git a/examples/react/virtualized-rows-experimental/index.html b/examples/react/virtualized-rows-experimental/index.html new file mode 100644 index 0000000000..1551290f72 --- /dev/null +++ b/examples/react/virtualized-rows-experimental/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/react/virtualized-rows-experimental/package.json b/examples/react/virtualized-rows-experimental/package.json new file mode 100644 index 0000000000..43953a3549 --- /dev/null +++ b/examples/react/virtualized-rows-experimental/package.json @@ -0,0 +1,28 @@ +{ + "name": "tanstack-react-table-example-virtualized-rows-experimental", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "@tanstack/react-virtual": "^3.13.24", + "react": "^19.2.6", + "react-dom": "^19.2.6" + }, + "devDependencies": { + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/react/virtualized-rows-experimental/src/index.css b/examples/react/virtualized-rows-experimental/src/index.css new file mode 100644 index 0000000000..720064ab9d --- /dev/null +++ b/examples/react/virtualized-rows-experimental/src/index.css @@ -0,0 +1,377 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border-collapse: collapse; + border-spacing: 0; + font-family: arial, sans-serif; + table-layout: fixed; +} + +thead { + background: lightgray; +} + +tr { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; + text-align: left; +} + +td { + padding: 6px; +} + +.container { + border: 1px solid lightgray; + margin: 1rem auto; +} + +.app { + margin: 1rem auto; + text-align: center; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/virtualized-rows-experimental/src/main.tsx b/examples/react/virtualized-rows-experimental/src/main.tsx new file mode 100644 index 0000000000..9a3bb35356 --- /dev/null +++ b/examples/react/virtualized-rows-experimental/src/main.tsx @@ -0,0 +1,312 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import { + FlexRender, + columnSizingFeature, + createSortedRowModel, + rowSortingFeature, + sortFns, + useTable, + createColumnHelper, +} from '@tanstack/react-table' +import { useVirtualizer } from '@tanstack/react-virtual' +import { makeData } from './makeData' +import type { Row, Table } from '@tanstack/react-table' +import type { Virtualizer } from '@tanstack/react-virtual' +import type { Person } from './makeData' +import './index.css' + +const _features = { + columnSizingFeature, + rowSortingFeature, +} + +const columnHelper = createColumnHelper() +// This is a dynamic row height example, which is more complicated, but allows for a more realistic table. +// See https://tanstack.com/virtual/v3/docs/examples/react/table for a simpler fixed row height example. +function App() { + const columns = React.useMemo( + () => + columnHelper.columns([ + columnHelper.accessor('id', { + header: 'ID', + size: 60, + }), + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + }), + columnHelper.accessor('age', { + header: () => 'Age', + size: 50, + }), + columnHelper.accessor('visits', { + header: () => Visits, + size: 50, + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + size: 80, + }), + columnHelper.accessor('createdAt', { + header: 'Created At', + cell: (info) => info.getValue().toLocaleString(), + size: 250, + }), + ]), + [], + ) + + const [data, setData] = React.useState(() => makeData(50_000)) + const refreshData = () => setData(makeData(50_000)) + const stressTest = () => setData(makeData(500_000)) + + const table = useTable( + { + _features, + _rowModels: { sortedRowModel: createSortedRowModel(sortFns) }, + columns, + data, + debugTable: true, + }, + (state) => state, // default selector + ) + + // The virtualizer needs to know the scrollable container element + const tableContainerRef = React.useRef(null) + + // All important CSS styles are included as inline styles for this example. This is not recommended for your code. + return ( +
+ {process.env.NODE_ENV === 'development' ? ( +

+ Notice: You are currently running React in + development mode. Virtualized rendering performance will be slightly + degraded until this application is built for production. +

+ ) : null} +
+ + +
+ ({data.length.toLocaleString()} rows) +
+ {/* Even though we're still using sematic table tags, we must use CSS grid and flexbox for dynamic row heights */} + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ) + })} + + ))} + + +
+
+ + {{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null} +
+
+
+
+ ) +} + +interface TableBodyWrapperProps { + table: Table + tableContainerRef: React.RefObject +} + +function TableBodyWrapper({ table, tableContainerRef }: TableBodyWrapperProps) { + const rowRefsMap = React.useRef>(new Map()) + + const { rows } = table.getRowModel() + + const rowVirtualizer = useVirtualizer({ + count: rows.length, + estimateSize: () => 33, // estimate row height for accurate scrollbar dragging + getScrollElement: () => tableContainerRef.current, + // measure dynamic row height, except in firefox because it measures table border height incorrectly + measureElement: + typeof window !== 'undefined' && + navigator.userAgent.indexOf('Firefox') === -1 + ? (element) => element.getBoundingClientRect().height + : undefined, + overscan: 5, + onChange: (instance) => { + // requestAnimationFrame(() => { + instance.getVirtualItems().forEach((virtualRow) => { + const rowRef = rowRefsMap.current.get(virtualRow.index) + if (!rowRef) return + rowRef.style.transform = `translateY(${virtualRow.start}px)` + }) + // }) + }, + }) + + React.useLayoutEffect(() => { + rowVirtualizer.measure() + }, [table.store.state]) + + return ( + + ) +} + +interface TableBodyProps { + table: Table + rowVirtualizer: Virtualizer + rowRefsMap: React.MutableRefObject> +} + +function TableBody({ rowVirtualizer, table, rowRefsMap }: TableBodyProps) { + const { rows } = table.getRowModel() + const virtualRowIndexes = rowVirtualizer.getVirtualIndexes() + + return ( + + {virtualRowIndexes.map((virtualRowIndex) => { + const row = rows[virtualRowIndex] + return ( + + ) + })} + + ) +} + +interface TableBodyRowProps { + row: Row + rowRefsMap: React.MutableRefObject> + rowVirtualizer: Virtualizer + virtualRowIndex: number +} + +function TableBodyRow({ + row, + rowRefsMap, + rowVirtualizer, + virtualRowIndex, +}: TableBodyRowProps) { + return ( + { + if (node && typeof virtualRowIndex !== 'undefined') { + rowVirtualizer.measureElement(node) // measure dynamic row height + rowRefsMap.current.set(virtualRowIndex, node) // store ref for virtualizer to apply scrolling transforms + } + }} + key={row.id} + style={{ + display: 'flex', + position: 'absolute', + width: '100%', + }} + > + {row.getAllCells().map((cell) => { + return ( + + + + ) + })} + + ) +} + +// test out when rows don't re-render at all (future TanStack Virtual release can make this unnecessary) +const TableBodyRowMemo = React.memo( + TableBodyRow, + (_prev, next) => next.rowVirtualizer.isScrolling, +) as typeof TableBodyRow + +const rootElement = document.getElementById('root') + +if (!rootElement) throw new Error('Failed to find the root element') + +ReactDOM.createRoot(rootElement).render( + // + , + // , +) diff --git a/examples/react/virtualized-rows-experimental/src/makeData.ts b/examples/react/virtualized-rows-experimental/src/makeData.ts new file mode 100644 index 0000000000..e5695467f5 --- /dev/null +++ b/examples/react/virtualized-rows-experimental/src/makeData.ts @@ -0,0 +1,50 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + id: number + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + createdAt: Date +} + +const range = (len: number) => { + const arr: number[] = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (index: number): Person => { + return { + id: index + 1, + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + createdAt: faker.date.anytime(), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0]!, + } +} + +export function makeData(...lens: number[]) { + const makeDataLevel = (depth = 0): Person[] => { + const len = lens[depth]! + return range(len).map((d): Person => { + return { + ...newPerson(d), + } + }) + } + + return makeDataLevel() +} diff --git a/examples/react/virtualized-rows-experimental/src/vite-env.d.ts b/examples/react/virtualized-rows-experimental/src/vite-env.d.ts new file mode 100644 index 0000000000..d1145797cd --- /dev/null +++ b/examples/react/virtualized-rows-experimental/src/vite-env.d.ts @@ -0,0 +1,7 @@ +/// + +declare const process: { + readonly env: { + readonly NODE_ENV?: string + } +} diff --git a/examples/react/virtualized-rows-experimental/tsconfig.json b/examples/react/virtualized-rows-experimental/tsconfig.json new file mode 100644 index 0000000000..33a3e872be --- /dev/null +++ b/examples/react/virtualized-rows-experimental/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/react/virtualized-rows-experimental/vite.config.js b/examples/react/virtualized-rows-experimental/vite.config.js new file mode 100644 index 0000000000..273808b8d3 --- /dev/null +++ b/examples/react/virtualized-rows-experimental/vite.config.js @@ -0,0 +1,23 @@ +import { defineConfig } from 'vite' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + react(), + // React Compiler - comment out the next line to disable + // babel({ + // presets: [reactCompilerPreset()], + // include: [/\/src\/.*\.[jt]sx?$/], + // }), + ], +}) diff --git a/examples/react/virtualized-rows/index.html b/examples/react/virtualized-rows/index.html index 3fc40c9367..1551290f72 100644 --- a/examples/react/virtualized-rows/index.html +++ b/examples/react/virtualized-rows/index.html @@ -4,7 +4,7 @@ Vite App - +
diff --git a/examples/react/virtualized-rows/package.json b/examples/react/virtualized-rows/package.json index 9cf6df0b17..d9f0dc8e20 100644 --- a/examples/react/virtualized-rows/package.json +++ b/examples/react/virtualized-rows/package.json @@ -1,26 +1,29 @@ { - "name": "tanstack-table-example-virtualized-rows", - "version": "0.0.0", + "name": "tanstack-react-table-example-virtualized-rows", "private": true, "scripts": { "dev": "vite", "build": "vite build", "serve": "vite preview", - "start": "vite" + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" }, "dependencies": { - "@faker-js/faker": "^8.4.1", - "@tanstack/react-table": "^8.20.6", - "@tanstack/react-virtual": "^3.8.1", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "@faker-js/faker": "^10.4.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "@tanstack/react-virtual": "^3.13.24", + "react": "^19.2.6", + "react-dom": "^19.2.6" }, "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/react/virtualized-rows/src/index.css b/examples/react/virtualized-rows/src/index.css index 6402cb4b8b..720064ab9d 100644 --- a/examples/react/virtualized-rows/src/index.css +++ b/examples/react/virtualized-rows/src/index.css @@ -38,3 +38,340 @@ td { margin: 1rem auto; text-align: center; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/virtualized-rows/src/main.tsx b/examples/react/virtualized-rows/src/main.tsx index 630f3775de..df7f639216 100644 --- a/examples/react/virtualized-rows/src/main.tsx +++ b/examples/react/virtualized-rows/src/main.tsx @@ -1,209 +1,264 @@ -import React from 'react' +import React, { useEffect } from 'react' import ReactDOM from 'react-dom/client' import './index.css' import { - ColumnDef, - flexRender, - getCoreRowModel, - getSortedRowModel, - Row, - useReactTable, + columnSizingFeature, + createColumnHelper, + createSortedRowModel, + rowSortingFeature, + sortFns, + useTable, } from '@tanstack/react-table' - import { useVirtualizer } from '@tanstack/react-virtual' +import { makeData } from './makeData' +import type { ReactTable, Row } from '@tanstack/react-table' +import type { VirtualItem, Virtualizer } from '@tanstack/react-virtual' +import type { Person } from './makeData' -import { makeData, Person } from './makeData' +const features = { + columnSizingFeature, + rowSortingFeature, +} -//This is a dynamic row height example, which is more complicated, but allows for a more realistic table. -//See https://tanstack.com/virtual/v3/docs/examples/react/table for a simpler fixed row height example. +const columnHelper = createColumnHelper() +// This is a dynamic row height example, which is more complicated, but allows for a more realistic table. +// See https://tanstack.com/virtual/v3/docs/examples/react/table for a simpler fixed row height example. function App() { - const columns = React.useMemo[]>( - () => [ - { - accessorKey: 'id', - header: 'ID', - size: 60, - }, - { - accessorKey: 'firstName', - cell: info => info.getValue(), - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - cell: info => info.getValue(), - header: () => Last Name, - }, - { - accessorKey: 'age', - header: () => 'Age', - size: 50, - }, - { - accessorKey: 'visits', - header: () => Visits, - size: 50, - }, - { - accessorKey: 'status', - header: 'Status', - }, - { - accessorKey: 'progress', - header: 'Profile Progress', - size: 80, - }, - { - accessorKey: 'createdAt', - header: 'Created At', - cell: info => info.getValue().toLocaleString(), - size: 250, - }, - ], - [] + const columns = React.useMemo( + () => + columnHelper.columns([ + columnHelper.accessor('id', { + header: 'ID', + size: 60, + }), + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + }), + columnHelper.accessor('age', { + header: () => 'Age', + size: 50, + }), + columnHelper.accessor('visits', { + header: () => Visits, + size: 50, + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + size: 80, + }), + columnHelper.accessor('createdAt', { + header: 'Created At', + cell: (info) => info.getValue().toLocaleString(), + size: 250, + }), + ]), + [], ) - const [data, _setData] = React.useState(() => makeData(50_000)) + // The virtualizer will need a reference to the scrollable container element + const tableContainerRef = React.useRef(null) - const table = useReactTable({ - data, - columns, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), - debugTable: true, - }) + const [data, setData] = React.useState(() => makeData(200_000)) - const { rows } = table.getRowModel() + const refreshData = React.useCallback(() => { + setData(makeData(200_000)) + }, []) - //The virtualizer needs to know the scrollable container element - const tableContainerRef = React.useRef(null) + const stressTest = React.useCallback(() => { + setData(makeData(1_000_000)) + }, []) - const rowVirtualizer = useVirtualizer({ - count: rows.length, - estimateSize: () => 33, //estimate row height for accurate scrollbar dragging - getScrollElement: () => tableContainerRef.current, - //measure dynamic row height, except in firefox because it measures table border height incorrectly - measureElement: - typeof window !== 'undefined' && - navigator.userAgent.indexOf('Firefox') === -1 - ? element => element?.getBoundingClientRect().height - : undefined, - overscan: 5, - }) + const table = useTable( + { + _features: features, + _rowModels: { sortedRowModel: createSortedRowModel(sortFns) }, + columns, + data, + debugTable: true, + }, + (state) => state, // default selector + ) - //All important CSS styles are included as inline styles for this example. This is not recommended for your code. + // All important CSS styles are included as inline styles for this example. This is not recommended for your code. return ( -
- {process.env.NODE_ENV === 'development' ? ( -

- Notice: You are currently running React in - development mode. Virtualized rendering performance will be slightly - degraded until this application is built for production. -

- ) : null} - ({data.length} rows) -
- {/* Even though we're still using sematic table tags, we must use CSS grid and flexbox for dynamic row heights */} - - - {table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(header => { - return ( - - ) - })} - - ))} - - - {rowVirtualizer.getVirtualItems().map(virtualRow => { - const row = rows[virtualRow.index] as Row - return ( + <> +
+ {process.env.NODE_ENV === 'development' ? ( +

+ Notice: You are currently running React in + development mode. Virtualized rendering performance will be slightly + degraded until this application is built for production. +

+ ) : null} + ({data.length.toLocaleString()} rows) +
+ + +
+
+ {/* Even though we're still using sematic table tags, we must use CSS grid and flexbox for dynamic row heights */} +
-
- {flexRender( - header.column.columnDef.header, - header.getContext() - )} - {{ - asc: ' 🔼', - desc: ' 🔽', - }[header.column.getIsSorted() as string] ?? null} -
-
+ + {table.getHeaderGroups().map((headerGroup) => ( rowVirtualizer.measureElement(node)} //measure dynamic row height - key={row.id} - style={{ - display: 'flex', - position: 'absolute', - transform: `translateY(${virtualRow.start}px)`, //this should always be a `style` as it changes on scroll - width: '100%', - }} + key={headerGroup.id} + style={{ display: 'flex', height: '34px', width: '100%' }} > - {row.getVisibleCells().map(cell => { + {headerGroup.headers.map((header) => { return ( - +
+ + {{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null} +
+ ) })} - ) - })} - -
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} -
+ ))} + + + +
-
+
{JSON.stringify(table.state, null, 2)}
+ + ) +} + +interface TableBodyProps { + table: ReactTable + tableContainerRef: React.RefObject +} + +function TableBody({ table, tableContainerRef }: TableBodyProps) { + const { rows } = table.getRowModel() + + // Important: Keep the row virtualizer in the lowest component possible to avoid unnecessary re-renders. + const rowVirtualizer = useVirtualizer({ + count: rows.length, + estimateSize: () => 33, // estimate row height for accurate scrollbar dragging + getScrollElement: () => tableContainerRef.current, + // measure dynamic row height, except in firefox because it measures table border height incorrectly + measureElement: + typeof window !== 'undefined' && + navigator.userAgent.indexOf('Firefox') === -1 + ? (element) => element.getBoundingClientRect().height + : undefined, + overscan: 5, + }) + + useEffect(() => { + rowVirtualizer.measure() + }, []) + + return ( + + {rowVirtualizer.getVirtualItems().map((virtualRow) => { + const row = rows[virtualRow.index] + return ( + + ) + })} + + ) +} + +interface TableBodyRowProps { + row: Row + virtualRow: VirtualItem + rowVirtualizer: Virtualizer + table: ReactTable +} + +function TableBodyRow({ + row, + virtualRow, + rowVirtualizer, + table, +}: TableBodyRowProps) { + return ( + rowVirtualizer.measureElement(node)} // measure dynamic row height + key={row.id} + style={{ + display: 'flex', + position: 'absolute', + transform: `translateY(${virtualRow.start}px)`, // this should always be a `style` as it changes on scroll + width: '100%', + }} + > + {row.getAllCells().map((cell) => { + return ( + + + + ) + })} + ) } @@ -214,5 +269,5 @@ if (!rootElement) throw new Error('Failed to find the root element') ReactDOM.createRoot(rootElement).render( - + , ) diff --git a/examples/react/virtualized-rows/src/makeData.ts b/examples/react/virtualized-rows/src/makeData.ts index e5695467f5..5fd0293fef 100644 --- a/examples/react/virtualized-rows/src/makeData.ts +++ b/examples/react/virtualized-rows/src/makeData.ts @@ -12,7 +12,7 @@ export type Person = { } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -32,13 +32,13 @@ const newPerson = (index: number): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] return range(len).map((d): Person => { return { ...newPerson(d), diff --git a/examples/react/virtualized-rows/src/vite-env.d.ts b/examples/react/virtualized-rows/src/vite-env.d.ts new file mode 100644 index 0000000000..d1145797cd --- /dev/null +++ b/examples/react/virtualized-rows/src/vite-env.d.ts @@ -0,0 +1,7 @@ +/// + +declare const process: { + readonly env: { + readonly NODE_ENV?: string + } +} diff --git a/examples/react/virtualized-rows/tsconfig.json b/examples/react/virtualized-rows/tsconfig.json index 6d545f543f..33a3e872be 100644 --- a/examples/react/virtualized-rows/tsconfig.json +++ b/examples/react/virtualized-rows/tsconfig.json @@ -5,20 +5,17 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, - "include": ["src"] + "include": ["src", "vite.config.js", "vite.config.ts"] } diff --git a/examples/react/virtualized-rows/vite.config.js b/examples/react/virtualized-rows/vite.config.js index 2e1361723a..1755f6bea8 100644 --- a/examples/react/virtualized-rows/vite.config.js +++ b/examples/react/virtualized-rows/vite.config.js @@ -1,5 +1,6 @@ import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' import rollupReplace from '@rollup/plugin-replace' // https://vitejs.dev/config/ @@ -13,5 +14,10 @@ export default defineConfig({ }, }), react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), ], }) diff --git a/examples/react/with-tanstack-form/.gitignore b/examples/react/with-tanstack-form/.gitignore new file mode 100644 index 0000000000..d451ff16c1 --- /dev/null +++ b/examples/react/with-tanstack-form/.gitignore @@ -0,0 +1,5 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local diff --git a/examples/react/with-tanstack-form/README.md b/examples/react/with-tanstack-form/README.md new file mode 100644 index 0000000000..59c7bc9ab0 --- /dev/null +++ b/examples/react/with-tanstack-form/README.md @@ -0,0 +1,25 @@ +# Editable Data with TanStack Form + +This example demonstrates integrating TanStack Form with TanStack Table for editable data management. + +## Features + +- **Form-based editing**: Each table cell is a form field managed by TanStack Form +- **Array field management**: Table data is stored as a form array with indexed field access +- **Validation**: Per-field Zod validation with error display +- **Form state tracking**: Dirty/pristine and valid/invalid indicators +- **Pagination & Filtering**: Full table features work with form-managed data + +## Key Patterns + +- `useAppForm` with `defaultValues: { data: [...] }` for array data +- `form.AppField name="data[${row.index}].fieldName"` for cell editing +- `table.Subscribe` for reactive table state +- `table.FlexRender` for cell rendering + +## Running the Example + +```bash +pnpm install +pnpm dev +``` diff --git a/examples/react/with-tanstack-form/index.html b/examples/react/with-tanstack-form/index.html new file mode 100644 index 0000000000..1551290f72 --- /dev/null +++ b/examples/react/with-tanstack-form/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/react/with-tanstack-form/package.json b/examples/react/with-tanstack-form/package.json new file mode 100644 index 0000000000..917e20de45 --- /dev/null +++ b/examples/react/with-tanstack-form/package.json @@ -0,0 +1,31 @@ +{ + "name": "tanstack-react-table-example-with-tanstack-form", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/react-form": "^1.31.0", + "@tanstack/react-pacer": "^0.22.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6", + "zod": "^4.4.3" + }, + "devDependencies": { + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/react/with-tanstack-form/src/form.tsx b/examples/react/with-tanstack-form/src/form.tsx new file mode 100644 index 0000000000..1132637528 --- /dev/null +++ b/examples/react/with-tanstack-form/src/form.tsx @@ -0,0 +1,141 @@ +import { + createFormHook, + createFormHookContexts, + useStore, +} from '@tanstack/react-form' + +// Create form and field contexts +export const { fieldContext, useFieldContext, formContext, useFormContext } = + createFormHookContexts() + +// TextField component for string inputs +function TextField() { + const field = useFieldContext() + const errors = useStore(field.store, (state) => state.meta.errors) + + return ( +
+ field.handleChange(e.target.value)} + onBlur={field.handleBlur} + /> + {errors.length > 0 && ( +
{errors.join(', ')}
+ )} +
+ ) +} + +// NumberField component for numeric inputs +function NumberField() { + const field = useFieldContext() + const errors = useStore(field.store, (state) => state.meta.errors) + + return ( +
+ field.handleChange(Number(e.target.value))} + onBlur={field.handleBlur} + /> + {errors.length > 0 && ( +
{errors.join(', ')}
+ )} +
+ ) +} + +// SelectField component for status dropdown +const statusOptions = ['relationship', 'complicated', 'single'] as const + +function SelectField() { + const field = useFieldContext() + const errors = useStore(field.store, (state) => state.meta.errors) + + return ( +
+ + {errors.length > 0 && ( +
{errors.join(', ')}
+ )} +
+ ) +} + +// SubmitButton component that shows form state +function SubmitButton({ label }: { label: string }) { + const form = useFormContext() + return ( + [state.isSubmitting, state.canSubmit]}> + {([isSubmitting, canSubmit]) => ( + + )} + + ) +} + +// FormStateIndicator component to show dirty/valid state +function FormStateIndicator() { + const form = useFormContext() + return ( + ({ + isDirty: state.isDirty, + isValid: state.isValid, + errorMap: state.errorMap, + })} + > + {({ isDirty, isValid, errorMap }) => ( +
+ + {isDirty ? '● Modified' : '○ Pristine'} + + + {isValid ? '✓ Valid' : '✗ Invalid'} + + {Object.keys(errorMap).length > 0 && ( + + Errors: {JSON.stringify(errorMap)} + + )} +
+ )} +
+ ) +} + +// Create the form hook with all components +export const { useAppForm, withForm } = createFormHook({ + fieldComponents: { + TextField, + NumberField, + SelectField, + }, + formComponents: { + SubmitButton, + FormStateIndicator, + }, + fieldContext, + formContext, +}) diff --git a/examples/react/with-tanstack-form/src/index.css b/examples/react/with-tanstack-form/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/react/with-tanstack-form/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/with-tanstack-form/src/main.tsx b/examples/react/with-tanstack-form/src/main.tsx new file mode 100644 index 0000000000..5bf79d6b06 --- /dev/null +++ b/examples/react/with-tanstack-form/src/main.tsx @@ -0,0 +1,450 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import { + columnFilteringFeature, + createColumnHelper, + createFilteredRowModel, + createPaginatedRowModel, + filterFns, + rowPaginationFeature, + tableFeatures, + useTable, +} from '@tanstack/react-table' +import { useDebouncedCallback } from '@tanstack/react-pacer/debouncer' +import { useStore } from '@tanstack/react-form' +import { z } from 'zod' +import { makeData } from './makeData' +import { useAppForm } from './form' +import type { Column, Table } from '@tanstack/react-table' +import type { Person } from './makeData' +import './index.css' + +/** + * `Person` includes optional recursive `subRows`; the form is a flat list only. Without `Omit`, + * TanStack Form's `DeepKeys` chases that recursion and TypeScript reports TS2589. + */ +type FormRow = Omit + +// Define table features +const _features = tableFeatures({ + rowPaginationFeature, + columnFilteringFeature, +}) + +// Create column helper with features and row type +const columnHelper = createColumnHelper() + +// Zod validation schema for a person +const personSchema = z.object({ + firstName: z.string().min(1, 'First name is required'), + lastName: z.string().min(1, 'Last name is required'), + age: z + .number() + .min(0, 'Age must be positive') + .max(150, 'Age must be realistic'), + visits: z.number().min(0, 'Visits must be positive'), + progress: z + .number() + .min(0, 'Progress must be 0-100') + .max(100, 'Progress must be 0-100'), + status: z.enum(['relationship', 'complicated', 'single']), +}) + +// Form data schema +const formSchema = z.object({ + data: z.array(personSchema), +}) + +function App() { + // Keep `data` typed as FormRow[] (not Person[]) so form field paths do not carry recursive `subRows` (TS2589). + const initialData: Array = makeData(100) + + const form = useAppForm({ + defaultValues: { + data: initialData, + }, + onSubmit: ({ value }) => { + alert( + `Submitted ${value.data.length} records!\n\nFirst record: ${JSON.stringify(value.data[0], null, 2)}`, + ) + }, + validators: { + onChange: formSchema, + }, + }) + + // Create columns with form fields for editing + const columns = React.useMemo( + () => + columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + footer: (props) => props.column.id, + cell: ({ row }) => ( + + {(field) => } + + ), + }), + columnHelper.accessor('lastName', { + header: () => Last Name, + footer: (props) => props.column.id, + cell: ({ row }) => ( + + {(field) => } + + ), + }), + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + cell: ({ row }) => ( + + {(field) => } + + ), + }), + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + cell: ({ row }) => ( + + {(field) => } + + ), + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + cell: ({ row }) => ( + + {(field) => } + + ), + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + cell: ({ row }) => ( + + {(field) => } + + ), + }), + ]), + [form], + ) + + // Subscribe only to array length to trigger re-renders when rows are added/removed + // This avoids infinite loops from subscribing to the entire data array + const dataLength = useStore(form.store, (state) => state.values.data.length) + void dataLength // Used to trigger re-renders, value not needed + + // Create table using form state as data source + // The table gets fresh data on each render, cells handle their own field state + const table = useTable( + { + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + data: form.state.values.data, + debugTable: true, + }, + (state) => state, // default selector + ) + + const refreshData = () => { + const data: Array = makeData(100) + form.reset({ data }) + } + + const stressTest = () => { + const data: Array = makeData(200_000) + form.reset({ data }) + } + + const addRow = () => { + form.pushFieldValue('data', { + firstName: '', + lastName: '', + age: 0, + visits: 0, + progress: 0, + status: 'single', + }) + } + + return ( +
+
{ + e.preventDefault() + e.stopPropagation() + form.handleSubmit() + }} + > + {/* Form state indicators */} +
+ + + + + + + + + +
+ + {/* Table */} + <> +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ) + })} + + ))} + + + {table.getRowModel().rows.map((row) => { + return ( + + {row.getAllCells().map((cell) => { + return ( + + ) + })} + + ) + })} + +
+ {header.isPlaceholder ? null : ( +
+ + {header.column.getCanFilter() ? ( +
+ +
+ ) : null} +
+ )} +
+ +
+ + {/* Pagination controls */} +
+
+ + + + + +
Page
+ + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = e.target.value ? Number(e.target.value) - 1 : 0 + table.setPageIndex(page) + }} + className="page-size-input" + /> + + +
+
+ Showing {table.getRowModel().rows.length.toLocaleString()} of{' '} + {table.getRowCount().toLocaleString()} Rows +
+ + +
+ ) +} + +function Filter({ + column, + table, +}: { + column: Column + table: Table +}) { + const firstValue = table + .getPreFilteredRowModel() + .flatRows[0]?.getValue(column.id) + + const columnFilterValue = column.getFilterValue() + + return typeof firstValue === 'number' ? ( +
+ + column.setFilterValue((old: [number, number]) => [value, old?.[1]]) + } + placeholder={`Min`} + className="filter-input" + /> + + column.setFilterValue((old: [number, number]) => [old?.[0], value]) + } + placeholder={`Max`} + className="filter-input" + /> +
+ ) : ( + column.setFilterValue(value)} + placeholder={`Search...`} + className="filter-select" + /> + ) +} + +// A debounced input react component +function DebouncedInput({ + value: initialValue, + onChange, + debounce = 500, + ...props +}: { + value: string | number + onChange: (value: string | number) => void + debounce?: number +} & Omit, 'onChange'>) { + const [value, setValue] = React.useState(initialValue) + + React.useEffect(() => { + setValue(initialValue) + }, [initialValue]) + + const debouncedOnChange = useDebouncedCallback(onChange, { wait: debounce }) + + return ( + { + setValue(e.target.value) + debouncedOnChange(e.target.value) + }} + /> + ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +ReactDOM.createRoot(rootElement).render( + + + , +) diff --git a/examples/react/with-tanstack-form/src/makeData.ts b/examples/react/with-tanstack-form/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/react/with-tanstack-form/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/react/with-tanstack-form/src/vite-env.d.ts b/examples/react/with-tanstack-form/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/with-tanstack-form/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/with-tanstack-form/tsconfig.json b/examples/react/with-tanstack-form/tsconfig.json new file mode 100644 index 0000000000..33a3e872be --- /dev/null +++ b/examples/react/with-tanstack-form/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/react/with-tanstack-form/vite.config.js b/examples/react/with-tanstack-form/vite.config.js new file mode 100644 index 0000000000..1755f6bea8 --- /dev/null +++ b/examples/react/with-tanstack-form/vite.config.js @@ -0,0 +1,23 @@ +import { defineConfig } from 'vite' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), + ], +}) diff --git a/examples/react/with-tanstack-query/.gitignore b/examples/react/with-tanstack-query/.gitignore new file mode 100644 index 0000000000..d451ff16c1 --- /dev/null +++ b/examples/react/with-tanstack-query/.gitignore @@ -0,0 +1,5 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local diff --git a/examples/react/with-tanstack-query/README.md b/examples/react/with-tanstack-query/README.md new file mode 100644 index 0000000000..b168d3c4b1 --- /dev/null +++ b/examples/react/with-tanstack-query/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run start` or `yarn start` diff --git a/examples/react/with-tanstack-query/index.html b/examples/react/with-tanstack-query/index.html new file mode 100644 index 0000000000..1551290f72 --- /dev/null +++ b/examples/react/with-tanstack-query/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/react/with-tanstack-query/package.json b/examples/react/with-tanstack-query/package.json new file mode 100644 index 0000000000..dee40ec1e9 --- /dev/null +++ b/examples/react/with-tanstack-query/package.json @@ -0,0 +1,30 @@ +{ + "name": "tanstack-react-table-example-with-tanstack-query", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/react-query": "^5.100.9", + "@tanstack/react-store": "^0.11.0", + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6" + }, + "devDependencies": { + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/react/with-tanstack-query/src/fetchData.ts b/examples/react/with-tanstack-query/src/fetchData.ts new file mode 100644 index 0000000000..eefb050404 --- /dev/null +++ b/examples/react/with-tanstack-query/src/fetchData.ts @@ -0,0 +1,67 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} + +const data = makeData(10000) + +export async function fetchData(options: { + pageIndex: number + pageSize: number +}) { + // Simulate some network latency + await new Promise((r) => setTimeout(r, 500)) + + return { + rows: data.slice( + options.pageIndex * options.pageSize, + (options.pageIndex + 1) * options.pageSize, + ), + pageCount: Math.ceil(data.length / options.pageSize), + rowCount: data.length, + } +} diff --git a/examples/react/with-tanstack-query/src/index.css b/examples/react/with-tanstack-query/src/index.css new file mode 100644 index 0000000000..443b3983a6 --- /dev/null +++ b/examples/react/with-tanstack-query/src/index.css @@ -0,0 +1,369 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +button:disabled { + opacity: 0.5; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/with-tanstack-query/src/main.tsx b/examples/react/with-tanstack-query/src/main.tsx new file mode 100644 index 0000000000..92f053b518 --- /dev/null +++ b/examples/react/with-tanstack-query/src/main.tsx @@ -0,0 +1,203 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import { + QueryClient, + QueryClientProvider, + keepPreviousData, + useQuery, +} from '@tanstack/react-query' +import { useCreateAtom, useSelector } from '@tanstack/react-store' +import './index.css' +import { + createColumnHelper, + rowPaginationFeature, + tableFeatures, + useTable, +} from '@tanstack/react-table' +import { fetchData } from './fetchData' +import type { PaginationState } from '@tanstack/react-table' +import type { Person } from './fetchData' + +const queryClient = new QueryClient() + +const _features = tableFeatures({ + rowPaginationFeature, +}) + +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('lastName', { + header: 'Last Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('age', { + header: 'Age', + }), + columnHelper.accessor('visits', { + header: 'Visits', + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + }), +]) + +function App() { + const rerender = React.useReducer(() => ({}), {})[1] + + // Create a stable external atom for the pagination slice. + const paginationAtom = useCreateAtom({ + pageIndex: 0, + pageSize: 10, + }) + + // Subscribe to the atom for reactive updates. + const pagination = useSelector(paginationAtom, (s) => s) + + const dataQuery = useQuery({ + queryKey: ['data', pagination], + queryFn: () => fetchData(pagination), + placeholderData: keepPreviousData, // don't have 0 rows flash while changing pages/loading next page + }) + + const defaultData = React.useMemo(() => [], []) + + const table = useTable( + { + _features, + _rowModels: {}, + columns, + data: dataQuery.data?.rows ?? defaultData, + rowCount: dataQuery.data?.rowCount, + atoms: { + pagination: paginationAtom, + }, + manualPagination: true, // we're doing manual "server-side" pagination + debugTable: true, + }, + (state) => state, // default selector + ) + + return ( +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getAllCells().map((cell) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( + + )} +
+ +
+
+
+ + + + + +
Page
+ + {(pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = e.target.value ? Number(e.target.value) - 1 : 0 + table.setPageIndex(page) + }} + className="page-size-input" + /> + + + {dataQuery.isFetching ? 'Loading...' : null} +
+
+ Showing {table.getRowModel().rows.length.toLocaleString()} of{' '} + {dataQuery.data?.rowCount.toLocaleString()} Rows +
+
+ +
+
{JSON.stringify({ pagination }, null, 2)}
+
+ ) +} + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +ReactDOM.createRoot(rootElement).render( + + + + + , +) diff --git a/examples/react/with-tanstack-query/src/vite-env.d.ts b/examples/react/with-tanstack-query/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/with-tanstack-query/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/with-tanstack-query/tsconfig.json b/examples/react/with-tanstack-query/tsconfig.json new file mode 100644 index 0000000000..33a3e872be --- /dev/null +++ b/examples/react/with-tanstack-query/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/react/with-tanstack-query/vite.config.js b/examples/react/with-tanstack-query/vite.config.js new file mode 100644 index 0000000000..1755f6bea8 --- /dev/null +++ b/examples/react/with-tanstack-query/vite.config.js @@ -0,0 +1,23 @@ +import { defineConfig } from 'vite' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), + ], +}) diff --git a/examples/react/with-tanstack-router/.gitignore b/examples/react/with-tanstack-router/.gitignore new file mode 100644 index 0000000000..d451ff16c1 --- /dev/null +++ b/examples/react/with-tanstack-router/.gitignore @@ -0,0 +1,5 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local diff --git a/examples/react/with-tanstack-router/README.md b/examples/react/with-tanstack-router/README.md new file mode 100644 index 0000000000..b168d3c4b1 --- /dev/null +++ b/examples/react/with-tanstack-router/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run start` or `yarn start` diff --git a/examples/react/with-tanstack-router/index.html b/examples/react/with-tanstack-router/index.html new file mode 100644 index 0000000000..1551290f72 --- /dev/null +++ b/examples/react/with-tanstack-router/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/react/with-tanstack-router/package.json b/examples/react/with-tanstack-router/package.json new file mode 100644 index 0000000000..c4f31bf3c0 --- /dev/null +++ b/examples/react/with-tanstack-router/package.json @@ -0,0 +1,32 @@ +{ + "name": "tanstack-react-table-example-with-tanstack-router", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "lint": "eslint ./src", + "test:types": "tsc" + }, + "dependencies": { + "@tanstack/react-pacer": "^0.22.0", + "@tanstack/react-query": "^5.100.9", + "@tanstack/react-router": "^1.169.2", + "@tanstack/react-table": "^9.0.0-alpha.45", + "react": "^19.2.6", + "react-dom": "^19.2.6" + }, + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "@rolldown/plugin-babel": "^0.2.3", + "@rollup/plugin-replace": "^6.0.3", + "@tanstack/router-vite-plugin": "^1.166.50", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/react/with-tanstack-router/src/App.tsx b/examples/react/with-tanstack-router/src/App.tsx new file mode 100644 index 0000000000..115de09e97 --- /dev/null +++ b/examples/react/with-tanstack-router/src/App.tsx @@ -0,0 +1,21 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { RouterProvider, createRouter } from '@tanstack/react-router' +import { routeTree } from './routeTree.gen' + +const router = createRouter({ routeTree }) + +declare module '@tanstack/react-router' { + interface Register { + router: typeof router + } +} + +const queryClient = new QueryClient() + +export default function App() { + return ( + + + + ) +} diff --git a/examples/react/query-router-search-params/src/api/data.json b/examples/react/with-tanstack-router/src/api/data.json similarity index 100% rename from examples/react/query-router-search-params/src/api/data.json rename to examples/react/with-tanstack-router/src/api/data.json diff --git a/examples/react/with-tanstack-router/src/api/types.ts b/examples/react/with-tanstack-router/src/api/types.ts new file mode 100644 index 0000000000..df769c0fc5 --- /dev/null +++ b/examples/react/with-tanstack-router/src/api/types.ts @@ -0,0 +1,10 @@ +import type { PaginationState } from '@tanstack/react-table' + +export type PaginatedData = { + result: Array + rowCount: number +} + +export type PaginationParams = PaginationState +export type SortParams = { sortBy: `${string}.${'asc' | 'desc'}` } +export type Filters = Partial diff --git a/examples/react/with-tanstack-router/src/api/user.ts b/examples/react/with-tanstack-router/src/api/user.ts new file mode 100644 index 0000000000..93aeb7bdb3 --- /dev/null +++ b/examples/react/with-tanstack-router/src/api/user.ts @@ -0,0 +1,76 @@ +import { faker } from '@faker-js/faker' +import type { Filters, PaginatedData } from './types' + +const DEFAULT_PAGE = 0 +const DEFAULT_PAGE_SIZE = 10 + +export type User = { + id: number + firstName: string + lastName: string + age: number +} + +export type UserFilters = Filters + +function makeData(amount: number): Array { + return Array(amount) + .fill(0) + .map((_, index) => { + return { + id: index + 1, + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + } + }) +} + +const data = makeData(1000) + +export async function fetchUsers( + filtersAndPagination: UserFilters, +): Promise> { + console.log('fetchUsers', filtersAndPagination) + const { + pageIndex = DEFAULT_PAGE, + pageSize = DEFAULT_PAGE_SIZE, + sortBy, + ...filters + } = filtersAndPagination + const requestedData = data.slice() + + if (sortBy) { + const [field, order] = sortBy.split('.') + requestedData.sort((a, b) => { + const aValue = a[field as keyof User] + const bValue = b[field as keyof User] + + if (aValue === bValue) return 0 + if (order === 'asc') return aValue > bValue ? 1 : -1 + return aValue < bValue ? 1 : -1 + }) + } + + const filteredData = requestedData.filter((user) => { + return Object.keys(filters).every((key) => { + const filter = filters[key as keyof User] + if (filter === undefined || filter === '') return true + + const value = user[key as keyof User] + if (typeof value === 'number') return value === +filter + + return value.toLowerCase().includes(`${filter}`.toLowerCase()) + }) + }) + + await new Promise((resolve) => setTimeout(resolve, 100)) + + return { + result: filteredData.slice( + pageIndex * pageSize, + (pageIndex + 1) * pageSize, + ), + rowCount: filteredData.length, + } +} diff --git a/examples/react/with-tanstack-router/src/components/debouncedInput.tsx b/examples/react/with-tanstack-router/src/components/debouncedInput.tsx new file mode 100644 index 0000000000..3c3edd2148 --- /dev/null +++ b/examples/react/with-tanstack-router/src/components/debouncedInput.tsx @@ -0,0 +1,40 @@ +import { useDebouncedCallback } from '@tanstack/react-pacer/debouncer' +import { useEffect, useState } from 'react' +import type { InputHTMLAttributes } from 'react' + +export function DebouncedInput({ + value: initialValue, + onChange, + debounce = 200, + ...props +}: { + value: string | number + onChange: (value: string | number) => void + debounce?: number +} & Omit, 'onChange'>) { + const [value, setValue] = useState(initialValue) + + useEffect(() => { + setValue(initialValue) + }, [initialValue]) + + const debouncedOnChange = useDebouncedCallback(onChange, { wait: debounce }) + + return ( + { + const nextValue = + e.target.value === '' + ? '' + : props.type === 'number' + ? e.target.valueAsNumber + : e.target.value + + setValue(nextValue) + debouncedOnChange(nextValue) + }} + /> + ) +} diff --git a/examples/react/with-tanstack-router/src/components/table.tsx b/examples/react/with-tanstack-router/src/components/table.tsx new file mode 100644 index 0000000000..63da294708 --- /dev/null +++ b/examples/react/with-tanstack-router/src/components/table.tsx @@ -0,0 +1,206 @@ +import { + columnFilteringFeature, + rowPaginationFeature, + rowSelectionFeature, + rowSortingFeature, + tableFeatures, + useTable, +} from '@tanstack/react-table' +import { useEffect } from 'react' +import { DebouncedInput } from './debouncedInput' +import type { + ColumnDef, + OnChangeFn, + PaginationState, + SortingState, + TableOptions_RowPagination, +} from '@tanstack/react-table' +import type { Filters } from '../api/types' + +export const _features = tableFeatures({ + columnFilteringFeature, + rowPaginationFeature, + rowSelectionFeature, + rowSortingFeature, +}) + +export const DEFAULT_PAGE_INDEX = 0 +export const DEFAULT_PAGE_SIZE = 10 + +type Props> = { + data: Array + columns: Array> + pagination: PaginationState + paginationOptions: Pick< + TableOptions_RowPagination, + 'onPaginationChange' | 'rowCount' + > + filters: Filters + onFilterChange: (dataFilters: Partial) => void + sorting: SortingState + onSortingChange: OnChangeFn +} + +export default function Table>({ + columns, + data, + filters, + onFilterChange, + onSortingChange, + pagination, + paginationOptions, + sorting, +}: Props) { + const table = useTable( + { + debugTable: true, + _features, + _rowModels: {}, // no client-side row models since we're doing server-side sorting, filtering, and pagination + columns, + data, + manualFiltering: true, + manualPagination: true, + manualSorting: true, + onSortingChange, + ...paginationOptions, + }, + (state) => state, // default selector + ) + + // Sync controlled state with per-slice base atoms + useEffect(() => { + table.baseAtoms.pagination.set(pagination) + table.baseAtoms.sorting.set(sorting) + }, [table, pagination, sorting]) + + return ( +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + const fieldMeta = header.column.columnDef.meta + return ( + + ) + })} + + ))} + + + {table.getRowModel().rows.map((row) => { + return ( + + {row.getAllCells().map((cell) => { + return ( + + ) + })} + + ) + })} + +
+ {header.isPlaceholder ? null : ( + <> +
+ + {{ + asc: ' 🔼', + desc: ' 🔽', + false: ' 🔃', + }[header.column.getIsSorted() as string] ?? null} +
+ {header.column.getCanFilter() && + fieldMeta?.filterKey !== undefined ? ( + { + onFilterChange({ + [fieldMeta.filterKey as keyof T]: value, + } as Partial) + }} + placeholder="Search..." + type={ + fieldMeta.filterVariant === 'number' + ? 'number' + : 'text' + } + value={filters[fieldMeta.filterKey] ?? ''} + /> + ) : null} + + )} +
+ +
+
+ + + + + +
Page
+ + {(table.store.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = e.target.value ? Number(e.target.value) - 1 : 0 + table.setPageIndex(page) + }} + className="page-size-input" + /> + + +
+
+ ) +} diff --git a/examples/react/with-tanstack-router/src/hooks/useFilters.ts b/examples/react/with-tanstack-router/src/hooks/useFilters.ts new file mode 100644 index 0000000000..6fdb20b110 --- /dev/null +++ b/examples/react/with-tanstack-router/src/hooks/useFilters.ts @@ -0,0 +1,26 @@ +import { getRouteApi } from '@tanstack/react-router' +import { cleanEmptyParams } from '../utils/cleanEmptyParams' +import type { RegisteredRouter, RouteIds } from '@tanstack/react-router' + +export function useFilters>( + routeId: T, +) { + const routeApi = getRouteApi(routeId) + const navigate = routeApi.useNavigate() + const filters = routeApi.useSearch() + + const setFilters = (partialFilters: Partial) => + navigate({ + search: (prev) => cleanEmptyParams({ ...prev, ...partialFilters }), + replace: true, + } as Parameters[0]) + + const resetFilters = () => { + navigate({ + search: {}, + replace: true, + } as Parameters[0]) + } + + return { filters, setFilters, resetFilters } +} diff --git a/examples/react/with-tanstack-router/src/index.css b/examples/react/with-tanstack-router/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/react/with-tanstack-router/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/react/with-tanstack-router/src/main.tsx b/examples/react/with-tanstack-router/src/main.tsx new file mode 100644 index 0000000000..83f06f31a1 --- /dev/null +++ b/examples/react/with-tanstack-router/src/main.tsx @@ -0,0 +1,13 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import './index.css' + +const rootElement = document.getElementById('root') +if (!rootElement) throw new Error('Failed to find the root element') + +ReactDOM.createRoot(rootElement).render( + + + , +) diff --git a/examples/react/with-tanstack-router/src/routeTree.gen.ts b/examples/react/with-tanstack-router/src/routeTree.gen.ts new file mode 100644 index 0000000000..f7374445c1 --- /dev/null +++ b/examples/react/with-tanstack-router/src/routeTree.gen.ts @@ -0,0 +1,77 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as AnotherRouteRouteImport } from './routes/anotherRoute' +import { Route as IndexRouteImport } from './routes/index' + +const AnotherRouteRoute = AnotherRouteRouteImport.update({ + id: '/anotherRoute', + path: '/anotherRoute', + getParentRoute: () => rootRouteImport, +} as any) +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/anotherRoute': typeof AnotherRouteRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/anotherRoute': typeof AnotherRouteRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/anotherRoute': typeof AnotherRouteRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/anotherRoute' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/anotherRoute' + id: '__root__' | '/' | '/anotherRoute' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + AnotherRouteRoute: typeof AnotherRouteRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/anotherRoute': { + id: '/anotherRoute' + path: '/anotherRoute' + fullPath: '/anotherRoute' + preLoaderRoute: typeof AnotherRouteRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + AnotherRouteRoute: AnotherRouteRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/examples/react/with-tanstack-router/src/routes/__root.tsx b/examples/react/with-tanstack-router/src/routes/__root.tsx new file mode 100644 index 0000000000..0558ee7c50 --- /dev/null +++ b/examples/react/with-tanstack-router/src/routes/__root.tsx @@ -0,0 +1,5 @@ +import { Outlet, createRootRoute } from '@tanstack/react-router' + +export const Route = createRootRoute({ + component: Outlet, +}) diff --git a/examples/react/query-router-search-params/src/routes/anotherRoute.tsx b/examples/react/with-tanstack-router/src/routes/anotherRoute.tsx similarity index 100% rename from examples/react/query-router-search-params/src/routes/anotherRoute.tsx rename to examples/react/with-tanstack-router/src/routes/anotherRoute.tsx diff --git a/examples/react/with-tanstack-router/src/routes/index.tsx b/examples/react/with-tanstack-router/src/routes/index.tsx new file mode 100644 index 0000000000..5ac11c123f --- /dev/null +++ b/examples/react/with-tanstack-router/src/routes/index.tsx @@ -0,0 +1,81 @@ +import { keepPreviousData, useQuery } from '@tanstack/react-query' +import { createFileRoute } from '@tanstack/react-router' +import { useMemo } from 'react' +import { fetchUsers } from '../api/user' +import Table, { + DEFAULT_PAGE_INDEX, + DEFAULT_PAGE_SIZE, +} from '../components/table' +import { useFilters } from '../hooks/useFilters' +import { sortByToState, stateToSortBy } from '../utils/tableSortMapper' +import { USER_COLUMNS } from '../utils/userColumns' +import type { UserFilters } from '../api/user' + +export const Route = createFileRoute('/')({ + component: UsersPage, + validateSearch: () => ({}) as UserFilters, +}) + +function UsersPage() { + const { filters, resetFilters, setFilters } = useFilters(Route.fullPath) + + const { data } = useQuery({ + queryKey: ['users', filters], + queryFn: () => fetchUsers(filters), + placeholderData: keepPreviousData, + }) + + const paginationState = { + pageIndex: filters.pageIndex ?? DEFAULT_PAGE_INDEX, + pageSize: filters.pageSize ?? DEFAULT_PAGE_SIZE, + } + const sortingState = sortByToState(filters.sortBy) + const columns = useMemo(() => USER_COLUMNS, []) + + return ( +
+

TanStack Table + Query + Router

+ { + setFilters( + typeof pagination === 'function' + ? pagination(paginationState) + : pagination, + ) + }, + rowCount: data?.rowCount, + }} + filters={filters} + onFilterChange={(filters) => + setFilters({ ...filters, pageIndex: DEFAULT_PAGE_INDEX }) + } + sorting={sortingState} + onSortingChange={(updaterOrValue) => { + const newSortingState = + typeof updaterOrValue === 'function' + ? updaterOrValue(sortingState) + : updaterOrValue + return setFilters({ + sortBy: stateToSortBy(newSortingState), + pageIndex: DEFAULT_PAGE_INDEX, + }) + }} + /> +
+ {data?.rowCount?.toLocaleString()} users found + +
+
{JSON.stringify(filters, null, 2)}
+ + ) +} diff --git a/examples/react/with-tanstack-router/src/utils/cleanEmptyParams.ts b/examples/react/with-tanstack-router/src/utils/cleanEmptyParams.ts new file mode 100644 index 0000000000..e3dc968bb8 --- /dev/null +++ b/examples/react/with-tanstack-router/src/utils/cleanEmptyParams.ts @@ -0,0 +1,21 @@ +import { DEFAULT_PAGE_INDEX, DEFAULT_PAGE_SIZE } from '../components/table' + +export const cleanEmptyParams = >( + search: T, +): T => { + const newSearch = { ...search } + Object.keys(newSearch).forEach((key) => { + const value = newSearch[key] + if ( + value === undefined || + value === '' || + (typeof value === 'number' && isNaN(value)) + ) + delete newSearch[key] + }) + + if (search.pageIndex === DEFAULT_PAGE_INDEX) delete newSearch.pageIndex + if (search.pageSize === DEFAULT_PAGE_SIZE) delete newSearch.pageSize + + return newSearch +} diff --git a/examples/react/with-tanstack-router/src/utils/tableSortMapper.ts b/examples/react/with-tanstack-router/src/utils/tableSortMapper.ts new file mode 100644 index 0000000000..db87b3b889 --- /dev/null +++ b/examples/react/with-tanstack-router/src/utils/tableSortMapper.ts @@ -0,0 +1,17 @@ +import type { SortingState } from '@tanstack/react-table' +import type { SortParams } from '../api/types' + +export const stateToSortBy = (sorting: SortingState | undefined) => { + if (!sorting || sorting.length == 0) return undefined + + const sort = sorting[0] + + return `${sort.id}.${sort.desc ? 'desc' : 'asc'}` as const +} + +export const sortByToState = (sortBy: SortParams['sortBy'] | undefined) => { + if (!sortBy) return [] + + const [id, desc] = sortBy.split('.') + return [{ id, desc: desc === 'desc' }] +} diff --git a/examples/react/with-tanstack-router/src/utils/userColumns.tsx b/examples/react/with-tanstack-router/src/utils/userColumns.tsx new file mode 100644 index 0000000000..897ca8c519 --- /dev/null +++ b/examples/react/with-tanstack-router/src/utils/userColumns.tsx @@ -0,0 +1,35 @@ +import { createColumnHelper } from '@tanstack/react-table' +import type { CellData, RowData, TableFeatures } from '@tanstack/react-table' +import type { User } from '../api/user' + +declare module '@tanstack/react-table' { + interface ColumnMeta< + TFeatures extends TableFeatures, + TData extends RowData, + TValue extends CellData = CellData, + > { + filterKey?: keyof TData + filterVariant?: 'text' | 'number' + } +} + +const columnHelper = createColumnHelper() + +export const USER_COLUMNS = columnHelper.columns([ + columnHelper.accessor('id', { + header: () => ID, + meta: { filterKey: 'id', filterVariant: 'number' }, + }), + columnHelper.accessor('firstName', { + header: () => First Name, + meta: { filterKey: 'firstName' }, + }), + columnHelper.accessor('lastName', { + header: () => Last Name, + meta: { filterKey: 'lastName' }, + }), + columnHelper.accessor('age', { + header: () => 'Age', + meta: { filterKey: 'age', filterVariant: 'number' }, + }), +]) diff --git a/examples/react/with-tanstack-router/src/vite-env.d.ts b/examples/react/with-tanstack-router/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/react/with-tanstack-router/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/with-tanstack-router/tsconfig.json b/examples/react/with-tanstack-router/tsconfig.json new file mode 100644 index 0000000000..33a3e872be --- /dev/null +++ b/examples/react/with-tanstack-router/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true + }, + "include": ["src", "vite.config.js", "vite.config.ts"] +} diff --git a/examples/react/with-tanstack-router/vite.config.js b/examples/react/with-tanstack-router/vite.config.js new file mode 100644 index 0000000000..d08a877609 --- /dev/null +++ b/examples/react/with-tanstack-router/vite.config.js @@ -0,0 +1,25 @@ +import { defineConfig } from 'vite' +import react, { reactCompilerPreset } from '@vitejs/plugin-react' +import babel from '@rolldown/plugin-babel' +import rollupReplace from '@rollup/plugin-replace' +import { TanStackRouterVite } from '@tanstack/router-vite-plugin' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + react(), + // React Compiler - comment out the next line to disable + babel({ + presets: [reactCompilerPreset()], + include: [/\/src\/.*\.[jt]sx?$/], + }), + TanStackRouterVite(), + ], +}) diff --git a/examples/solid/basic/.gitignore b/examples/solid/basic-app-table/.gitignore similarity index 100% rename from examples/solid/basic/.gitignore rename to examples/solid/basic-app-table/.gitignore diff --git a/examples/solid/basic-app-table/README.md b/examples/solid/basic-app-table/README.md new file mode 100644 index 0000000000..b168d3c4b1 --- /dev/null +++ b/examples/solid/basic-app-table/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run start` or `yarn start` diff --git a/examples/solid/basic/index.html b/examples/solid/basic-app-table/index.html similarity index 100% rename from examples/solid/basic/index.html rename to examples/solid/basic-app-table/index.html diff --git a/examples/solid/basic-app-table/package.json b/examples/solid/basic-app-table/package.json new file mode 100644 index 0000000000..4b9d68af55 --- /dev/null +++ b/examples/solid/basic-app-table/package.json @@ -0,0 +1,22 @@ +{ + "name": "tanstack-solid-table-example-basic-app-table", + "description": "", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ./src" + }, + "license": "MIT", + "devDependencies": { + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/solid-table": "^9.0.0-alpha.45", + "solid-js": "^1.9.12" + } +} diff --git a/examples/solid/basic-app-table/src/App.tsx b/examples/solid/basic-app-table/src/App.tsx new file mode 100644 index 0000000000..a48bb4d0b4 --- /dev/null +++ b/examples/solid/basic-app-table/src/App.tsx @@ -0,0 +1,125 @@ +import { createTableHook } from '@tanstack/solid-table' +import { For, createSignal } from 'solid-js' +import { makeData } from './makeData' +import type { Person } from './makeData' + +// 3. New in V9! Tell the table which features and row models we want to use. In this case, this will be a basic table with no additional features +const { createAppTable, createAppColumnHelper } = createTableHook({ + _features: {}, + _rowModels: {}, // client-side row models. `Core` row model is now included by default, but you can still override it here + debugTable: true, +}) + +// 4. Create a helper object to help define our columns +const columnHelper = createAppColumnHelper() + +// 5. Define the columns for your table with a stable reference (in this case, defined statically outside of a react component) +const columns = columnHelper.columns([ + // accessorKey method (most common for simple use-cases) + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (info) => info.column.id, + }), + // accessorFn used (alternative) along with a custom id + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => {info.getValue()}, + header: () => Last Name, + footer: (info) => info.column.id, + }), + // accessorFn used to transform the data + columnHelper.accessor((row) => Number(row.age), { + id: 'age', + header: () => 'Age', + cell: (info) => info.renderValue(), + footer: (info) => info.column.id, + }), + columnHelper.accessor('visits', { + header: () => Visits, + footer: (info) => info.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (info) => info.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (info) => info.column.id, + }), +]) + +export function App() { + // 6. Store data with a stable reference + const [data, setData] = createSignal(makeData(20)) + + const refreshData = () => setData(makeData(20)) + const stressTest = () => setData(makeData(1_000)) + + // 7. Create the table instance with the required columns and data. + // Features and row models are already defined in the createTableHook call above + const table = createAppTable({ + debugTable: true, + columns, + get data() { + return data() + }, + // add additional table options here or in the createTableHook call above + }) + + // 8. Render your table markup from the table instance APIs + return ( +
+
+ + +
+
+ + + {(headerGroup) => ( + + + {(header) => ( + + )} + + + )} + + + + + {(row) => ( + + + {(cell) => ( + + )} + + + )} + + + + + {(footerGroup) => ( + + + {(header) => ( + + )} + + + )} + + +
+ +
+ +
+ +
+
+ ) +} diff --git a/examples/solid/basic-app-table/src/index.css b/examples/solid/basic-app-table/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/solid/basic-app-table/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/basic-app-table/src/index.tsx b/examples/solid/basic-app-table/src/index.tsx new file mode 100644 index 0000000000..30c97d2fdb --- /dev/null +++ b/examples/solid/basic-app-table/src/index.tsx @@ -0,0 +1,5 @@ +import { render } from 'solid-js/web' +import './index.css' +import { App } from './App' + +render(() => , document.getElementById('root') as HTMLElement) diff --git a/examples/solid/basic-app-table/src/makeData.ts b/examples/solid/basic-app-table/src/makeData.ts new file mode 100644 index 0000000000..b9fb014aba --- /dev/null +++ b/examples/solid/basic-app-table/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/solid/basic-app-table/tsconfig.json b/examples/solid/basic-app-table/tsconfig.json new file mode 100644 index 0000000000..cb996fae69 --- /dev/null +++ b/examples/solid/basic-app-table/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts", "vite.config.js"] +} diff --git a/examples/solid/basic/vite.config.ts b/examples/solid/basic-app-table/vite.config.ts similarity index 100% rename from examples/solid/basic/vite.config.ts rename to examples/solid/basic-app-table/vite.config.ts diff --git a/examples/solid/basic-external-atoms/.gitignore b/examples/solid/basic-external-atoms/.gitignore new file mode 100644 index 0000000000..76add878f8 --- /dev/null +++ b/examples/solid/basic-external-atoms/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist \ No newline at end of file diff --git a/examples/solid/basic-external-atoms/README.md b/examples/solid/basic-external-atoms/README.md new file mode 100644 index 0000000000..b168d3c4b1 --- /dev/null +++ b/examples/solid/basic-external-atoms/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run start` or `yarn start` diff --git a/examples/solid/bootstrap/index.html b/examples/solid/basic-external-atoms/index.html similarity index 100% rename from examples/solid/bootstrap/index.html rename to examples/solid/basic-external-atoms/index.html diff --git a/examples/solid/basic-external-atoms/package.json b/examples/solid/basic-external-atoms/package.json new file mode 100644 index 0000000000..2dddb871c3 --- /dev/null +++ b/examples/solid/basic-external-atoms/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-solid-table-example-basic-external-atoms", + "description": "", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ./src" + }, + "license": "MIT", + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" + }, + "dependencies": { + "@tanstack/solid-store": "^0.11.0", + "@tanstack/solid-table": "^9.0.0-alpha.45", + "solid-js": "^1.9.12" + } +} diff --git a/examples/solid/basic-external-atoms/src/App.tsx b/examples/solid/basic-external-atoms/src/App.tsx new file mode 100644 index 0000000000..c11c737141 --- /dev/null +++ b/examples/solid/basic-external-atoms/src/App.tsx @@ -0,0 +1,215 @@ +import { + FlexRender, + createColumnHelper, + createPaginatedRowModel, + createSortedRowModel, + createTable, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/solid-table' +import { createAtom, useSelector } from '@tanstack/solid-store' +import { For, createSignal } from 'solid-js' +import { makeData } from './makeData' +import type { Person } from './makeData' +import type { PaginationState, SortingState } from '@tanstack/solid-table' + +// This example demonstrates managing individual slices of table state via +// external TanStack Store atoms. Each atom is a stand-alone, subscribable +// reactive cell — you can read, write, or subscribe to it from anywhere, +// which makes it convenient for sharing state across components or modules. + +const _features = tableFeatures({ + rowPaginationFeature, + rowSortingFeature, +}) + +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('lastName', { + header: 'Last Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('age', { + header: 'Age', + }), + columnHelper.accessor('visits', { + header: 'Visits', + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + }), +]) + +function App() { + const [data, setData] = createSignal(makeData(1_000)) + const refreshData = () => setData(makeData(1_000)) + const stressTest = () => setData(makeData(200_000)) + + // Create stable external atoms for the individual state slices you want to + // own. The table still creates internal base atoms for everything else. + const sortingAtom = createAtom([]) + const paginationAtom = createAtom({ + pageIndex: 0, + pageSize: 10, + }) + + // Subscribe to each atom independently — fine-grained Solid reactivity. + const sorting = useSelector(sortingAtom) + const pagination = useSelector(paginationAtom) + + // Create the table and pass your per-slice external atoms. + const table = createTable({ + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + get data() { + return data() + }, + atoms: { + sorting: sortingAtom, + pagination: paginationAtom, + }, + debugTable: true, + }) + + return ( +
+
+ + +
+ + + + {(headerGroup) => ( + + + {(header) => ( + + )} + + + )} + + + + + {(row) => ( + + + {(cell) => ( + + )} + + + )} + + +
+ {header.isPlaceholder ? null : ( +
+ + {{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null} +
+ )} +
+ +
+
+
+ + + + + +
Page
+ + {(pagination().pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = e.currentTarget.value + ? Number(e.currentTarget.value) - 1 + : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
+
+
+        {JSON.stringify(
+          { sorting: sorting(), pagination: pagination() },
+          null,
+          2,
+        )}
+      
+
+ ) +} + +export default App diff --git a/examples/solid/basic-external-atoms/src/index.css b/examples/solid/basic-external-atoms/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/solid/basic-external-atoms/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/basic-external-atoms/src/index.tsx b/examples/solid/basic-external-atoms/src/index.tsx new file mode 100644 index 0000000000..ea9c952177 --- /dev/null +++ b/examples/solid/basic-external-atoms/src/index.tsx @@ -0,0 +1,6 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import './index.css' +import App from './App' + +render(() => , document.getElementById('root') as HTMLElement) diff --git a/examples/solid/basic-external-atoms/src/makeData.ts b/examples/solid/basic-external-atoms/src/makeData.ts new file mode 100644 index 0000000000..6311127267 --- /dev/null +++ b/examples/solid/basic-external-atoms/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/solid/basic-external-atoms/tsconfig.json b/examples/solid/basic-external-atoms/tsconfig.json new file mode 100644 index 0000000000..cb996fae69 --- /dev/null +++ b/examples/solid/basic-external-atoms/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts", "vite.config.js"] +} diff --git a/examples/solid/bootstrap/vite.config.ts b/examples/solid/basic-external-atoms/vite.config.ts similarity index 100% rename from examples/solid/bootstrap/vite.config.ts rename to examples/solid/basic-external-atoms/vite.config.ts diff --git a/examples/solid/basic-external-state/.gitignore b/examples/solid/basic-external-state/.gitignore new file mode 100644 index 0000000000..76add878f8 --- /dev/null +++ b/examples/solid/basic-external-state/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist \ No newline at end of file diff --git a/examples/solid/basic-external-state/README.md b/examples/solid/basic-external-state/README.md new file mode 100644 index 0000000000..b168d3c4b1 --- /dev/null +++ b/examples/solid/basic-external-state/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run start` or `yarn start` diff --git a/examples/solid/basic-external-state/index.html b/examples/solid/basic-external-state/index.html new file mode 100644 index 0000000000..7d9b4f7c51 --- /dev/null +++ b/examples/solid/basic-external-state/index.html @@ -0,0 +1,16 @@ + + + + + + + + Solid App + + + +
+ + + + diff --git a/examples/solid/basic-external-state/package.json b/examples/solid/basic-external-state/package.json new file mode 100644 index 0000000000..a32adc4bb3 --- /dev/null +++ b/examples/solid/basic-external-state/package.json @@ -0,0 +1,22 @@ +{ + "name": "tanstack-solid-table-example-basic-external-state", + "description": "", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ./src" + }, + "license": "MIT", + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" + }, + "dependencies": { + "@tanstack/solid-table": "^9.0.0-alpha.45", + "solid-js": "^1.9.12" + } +} diff --git a/examples/solid/basic-external-state/src/App.tsx b/examples/solid/basic-external-state/src/App.tsx new file mode 100644 index 0000000000..71181031eb --- /dev/null +++ b/examples/solid/basic-external-state/src/App.tsx @@ -0,0 +1,214 @@ +import { + FlexRender, + createColumnHelper, + createPaginatedRowModel, + createSortedRowModel, + createTable, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/solid-table' +import { For, createSignal } from 'solid-js' +import { makeData } from './makeData' +import type { PaginationState, SortingState } from '@tanstack/solid-table' +import type { Person } from './makeData' + +// This example demonstrates managing table state externally via Solid's createSignal instead of letting the table manage its own state internally. + +const _features = tableFeatures({ + rowPaginationFeature, + rowSortingFeature, +}) + +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('lastName', { + header: 'Last Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('age', { + header: 'Age', + }), + columnHelper.accessor('visits', { + header: 'Visits', + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + }), +]) + +function App() { + const [data, setData] = createSignal(makeData(1_000)) + const refreshData = () => setData(makeData(1_000)) + const stressTest = () => setData(makeData(200_000)) + + // Manage sorting state with createSignal + const [sorting, setSorting] = createSignal([]) + + // Manage pagination state with createSignal + const [pagination, setPagination] = createSignal({ + pageIndex: 0, + pageSize: 10, + }) + + // Create the table and pass state + onChange handlers + const table = createTable({ + debugTable: true, + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + get data() { + return data() + }, + state: { + get sorting() { + return sorting() // connect our sorting state back down to the table + }, + get pagination() { + return pagination() // connect our pagination state back down to the table + }, + }, + onSortingChange: setSorting, // raise sorting state changes to our own state management + onPaginationChange: setPagination, // raise pagination state changes to our own state management + }) + + return ( +
+
+ + +
+ + + + {(headerGroup) => ( + + + {(header) => ( + + )} + + + )} + + + + + {(row) => ( + + + {(cell) => ( + + )} + + + )} + + +
+ {header.isPlaceholder ? null : ( +
+ + {{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null} +
+ )} +
+ +
+
+
+ + + + + +
Page
+ + {(pagination().pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = e.currentTarget.value + ? Number(e.currentTarget.value) - 1 + : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
+
+
+        {JSON.stringify(
+          { sorting: sorting(), pagination: pagination() },
+          null,
+          2,
+        )}
+      
+
+ ) +} + +export default App diff --git a/examples/solid/basic-external-state/src/index.css b/examples/solid/basic-external-state/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/solid/basic-external-state/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/basic-external-state/src/index.tsx b/examples/solid/basic-external-state/src/index.tsx new file mode 100644 index 0000000000..ea9c952177 --- /dev/null +++ b/examples/solid/basic-external-state/src/index.tsx @@ -0,0 +1,6 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import './index.css' +import App from './App' + +render(() => , document.getElementById('root') as HTMLElement) diff --git a/examples/solid/basic-external-state/src/makeData.ts b/examples/solid/basic-external-state/src/makeData.ts new file mode 100644 index 0000000000..6311127267 --- /dev/null +++ b/examples/solid/basic-external-state/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/solid/basic-external-state/tsconfig.json b/examples/solid/basic-external-state/tsconfig.json new file mode 100644 index 0000000000..cb996fae69 --- /dev/null +++ b/examples/solid/basic-external-state/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts", "vite.config.js"] +} diff --git a/examples/solid/basic-external-state/vite.config.ts b/examples/solid/basic-external-state/vite.config.ts new file mode 100644 index 0000000000..d27427972d --- /dev/null +++ b/examples/solid/basic-external-state/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite' +import solidPlugin from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solidPlugin()], + build: { + target: 'esnext', + polyfillDynamicImport: false, + }, +}) diff --git a/examples/solid/basic-use-table/.gitignore b/examples/solid/basic-use-table/.gitignore new file mode 100644 index 0000000000..76add878f8 --- /dev/null +++ b/examples/solid/basic-use-table/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist \ No newline at end of file diff --git a/examples/solid/basic-use-table/README.md b/examples/solid/basic-use-table/README.md new file mode 100644 index 0000000000..b168d3c4b1 --- /dev/null +++ b/examples/solid/basic-use-table/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run start` or `yarn start` diff --git a/examples/solid/basic-use-table/index.html b/examples/solid/basic-use-table/index.html new file mode 100644 index 0000000000..7d9b4f7c51 --- /dev/null +++ b/examples/solid/basic-use-table/index.html @@ -0,0 +1,16 @@ + + + + + + + + Solid App + + + +
+ + + + diff --git a/examples/solid/basic-use-table/package.json b/examples/solid/basic-use-table/package.json new file mode 100644 index 0000000000..843cf6d74e --- /dev/null +++ b/examples/solid/basic-use-table/package.json @@ -0,0 +1,21 @@ +{ + "name": "tanstack-solid-table-example-basic-use-table", + "description": "", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ./src" + }, + "license": "MIT", + "devDependencies": { + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" + }, + "dependencies": { + "@tanstack/solid-table": "^9.0.0-alpha.45", + "solid-js": "^1.9.12" + } +} diff --git a/examples/solid/basic-use-table/src/App.tsx b/examples/solid/basic-use-table/src/App.tsx new file mode 100644 index 0000000000..9516462696 --- /dev/null +++ b/examples/solid/basic-use-table/src/App.tsx @@ -0,0 +1,167 @@ +import { FlexRender, createTable, tableFeatures } from '@tanstack/solid-table' +import { For, createSignal } from 'solid-js' +import type { ColumnDef } from '@tanstack/solid-table' + +// This example uses the standalone `createTable` function to create a table without the `createTableHook` util. + +// 1. Define what the shape of your data will be for each row +type Person = { + firstName: string + lastName: string + age: number + visits: number + status: string + progress: number +} + +// 2. Create some dummy data with a stable reference (this could be an API response stored in createSignal or similar) +const defaultData: Array = [ + { + firstName: 'tanner', + lastName: 'linsley', + age: 24, + visits: 100, + status: 'In Relationship', + progress: 50, + }, + { + firstName: 'tandy', + lastName: 'miller', + age: 40, + visits: 40, + status: 'Single', + progress: 80, + }, + { + firstName: 'joe', + lastName: 'dirte', + age: 45, + visits: 20, + status: 'Complicated', + progress: 10, + }, + { + firstName: 'kevin', + lastName: 'vandy', + age: 12, + visits: 100, + status: 'Single', + progress: 70, + }, +] + +// 3. New in V9! Tell the table which features and row models we want to use. In this case, this will be a basic table with no additional features +const _features = tableFeatures({}) // util method to create sharable TFeatures object/type + +// 4. Define the columns for your table. This uses the new `ColumnDef` type to define columns. Alternatively, check out the createTableHook/createAppColumnHelper util for an even more type-safe way to define columns. +const columns: Array> = [ + { + accessorKey: 'firstName', // accessorKey method (most common for simple use-cases) + header: 'First Name', + cell: (info) => info.getValue(), + }, + { + accessorFn: (row) => row.lastName, // accessorFn used (alternative) along with a custom id + id: 'lastName', + header: () => Last Name, + cell: (info) => {info.getValue()}, + }, + { + accessorFn: (row) => Number(row.age), // accessorFn used to transform the data + id: 'age', + header: () => 'Age', + cell: (info) => info.renderValue(), + }, + { + accessorKey: 'visits', + header: () => Visits, + }, + { + accessorKey: 'status', + header: 'Status', + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + }, +] + +function App() { + // 5. Store data with a reactive reference + const [data, setData] = createSignal([...defaultData]) + const rerender = () => setData([...defaultData]) + + // 6. Create the table instance with required _features, columns, and data + const table = createTable({ + debugTable: true, + _features, // new required option in V9. Tell the table which features you are importing and using (better tree-shaking) + _rowModels: {}, // `Core` row model is now included by default, but you can still override it here + columns, + get data() { + return data() + }, + }) + + // 7. Render your table markup from the table instance APIs + return ( +
+ + + + {(headerGroup) => ( + + + {(header) => ( + + )} + + + )} + + + + + {(row) => ( + + + {(cell) => ( + + )} + + + )} + + + + + {(footerGroup) => ( + + + {(header) => ( + + )} + + + )} + + +
+ {header.isPlaceholder ? null : ( + + )} +
+ +
+ {header.isPlaceholder ? null : ( + + )} +
+
+ +
+ ) +} + +export default App diff --git a/examples/solid/basic-use-table/src/index.css b/examples/solid/basic-use-table/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/solid/basic-use-table/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/basic-use-table/src/index.tsx b/examples/solid/basic-use-table/src/index.tsx new file mode 100644 index 0000000000..ea9c952177 --- /dev/null +++ b/examples/solid/basic-use-table/src/index.tsx @@ -0,0 +1,6 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import './index.css' +import App from './App' + +render(() => , document.getElementById('root') as HTMLElement) diff --git a/examples/solid/basic-use-table/tsconfig.json b/examples/solid/basic-use-table/tsconfig.json new file mode 100644 index 0000000000..cb996fae69 --- /dev/null +++ b/examples/solid/basic-use-table/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts", "vite.config.js"] +} diff --git a/examples/solid/basic-use-table/vite.config.ts b/examples/solid/basic-use-table/vite.config.ts new file mode 100644 index 0000000000..d27427972d --- /dev/null +++ b/examples/solid/basic-use-table/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite' +import solidPlugin from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solidPlugin()], + build: { + target: 'esnext', + polyfillDynamicImport: false, + }, +}) diff --git a/examples/solid/basic/package.json b/examples/solid/basic/package.json deleted file mode 100644 index 50b6f96808..0000000000 --- a/examples/solid/basic/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "tanstack-table-example-solid-basic", - "version": "0.0.0", - "description": "", - "scripts": { - "start": "vite", - "dev": "vite", - "build": "vite build", - "serve": "vite preview" - }, - "license": "MIT", - "devDependencies": { - "typescript": "5.4.5", - "vite": "^5.3.2", - "vite-plugin-solid": "^2.10.2" - }, - "dependencies": { - "@tanstack/solid-table": "^8.20.5", - "solid-js": "^1.8.18" - } -} diff --git a/examples/solid/basic/src/App.tsx b/examples/solid/basic/src/App.tsx deleted file mode 100644 index 8a988dcfc0..0000000000 --- a/examples/solid/basic/src/App.tsx +++ /dev/null @@ -1,162 +0,0 @@ -import { - flexRender, - getCoreRowModel, - ColumnDef, - createSolidTable, -} from '@tanstack/solid-table' -import { createSignal, For } from 'solid-js' - -type Person = { - firstName: string - lastName: string - age: number - visits: number - status: string - progress: number -} - -const defaultData: Person[] = [ - { - firstName: 'tanner', - lastName: 'linsley', - age: 24, - visits: 100, - status: 'In Relationship', - progress: 50, - }, - { - firstName: 'tandy', - lastName: 'miller', - age: 40, - visits: 40, - status: 'Single', - progress: 80, - }, - { - firstName: 'joe', - lastName: 'dirte', - age: 45, - visits: 20, - status: 'Complicated', - progress: 10, - }, -] - -const defaultColumns: ColumnDef[] = [ - { - accessorKey: 'firstName', - cell: info => info.getValue(), - footer: info => info.column.id, - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - cell: info => {info.getValue()}, - header: () => Last Name, - footer: info => info.column.id, - }, - { - accessorKey: 'age', - header: () => 'Age', - footer: info => info.column.id, - }, - { - accessorKey: 'visits', - header: () => Visits, - footer: info => info.column.id, - }, - { - accessorKey: 'status', - header: 'Status', - footer: info => info.column.id, - }, - { - accessorKey: 'progress', - header: 'Profile Progress', - footer: info => info.column.id, - }, -] - -function App() { - const [data, setData] = createSignal(defaultData) - const rerender = () => setData(defaultData) - - const table = createSolidTable({ - get data() { - return data() - }, - columns: defaultColumns, - getCoreRowModel: getCoreRowModel(), - }) - - return ( -
- - - - {headerGroup => ( - - - {header => ( - - )} - - - )} - - - - - {row => ( - - - {cell => ( - - )} - - - )} - - - - - {footerGroup => ( - - - {header => ( - - )} - - - )} - - -
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} -
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} -
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.footer, - header.getContext() - )} -
-
- -
- ) -} - -export default App diff --git a/examples/solid/basic/src/index.css b/examples/solid/basic/src/index.css deleted file mode 100644 index 43c09e0f6b..0000000000 --- a/examples/solid/basic/src/index.css +++ /dev/null @@ -1,26 +0,0 @@ -html { - font-family: sans-serif; - font-size: 14px; -} - -table { - border: 1px solid lightgray; -} - -tbody { - border-bottom: 1px solid lightgray; -} - -th { - border-bottom: 1px solid lightgray; - border-right: 1px solid lightgray; - padding: 2px 4px; -} - -tfoot { - color: gray; -} - -tfoot th { - font-weight: normal; -} diff --git a/examples/solid/basic/src/index.tsx b/examples/solid/basic/src/index.tsx deleted file mode 100644 index b5618861da..0000000000 --- a/examples/solid/basic/src/index.tsx +++ /dev/null @@ -1,7 +0,0 @@ -/* @refresh reload */ -import { render } from 'solid-js/web' - -import './index.css' -import App from './App' - -render(() => , document.getElementById('root') as HTMLElement) diff --git a/examples/solid/basic/tsconfig.json b/examples/solid/basic/tsconfig.json deleted file mode 100644 index 7ab027b8d2..0000000000 --- a/examples/solid/basic/tsconfig.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "module": "ESNext", - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "preserve", - "jsxImportSource": "solid-js", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"] -} diff --git a/examples/solid/bootstrap/package.json b/examples/solid/bootstrap/package.json deleted file mode 100644 index db906986a7..0000000000 --- a/examples/solid/bootstrap/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "tanstack-table-example-solid-bootstrap", - "version": "0.0.0", - "description": "", - "scripts": { - "start": "vite", - "dev": "vite", - "build": "vite build", - "serve": "vite preview" - }, - "license": "MIT", - "devDependencies": { - "@faker-js/faker": "^8.4.1", - "typescript": "5.4.5", - "vite": "^5.3.2", - "vite-plugin-solid": "^2.10.2" - }, - "dependencies": { - "@tanstack/solid-table": "^8.20.5", - "bootstrap": "^5.3.3", - "solid-bootstrap": "^1.0.20", - "solid-js": "^1.8.18" - } -} diff --git a/examples/solid/bootstrap/src/App.tsx b/examples/solid/bootstrap/src/App.tsx deleted file mode 100644 index 781adc6daa..0000000000 --- a/examples/solid/bootstrap/src/App.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import { - flexRender, - getCoreRowModel, - ColumnDef, - createSolidTable, -} from '@tanstack/solid-table' -import { createSignal, For } from 'solid-js' -import { makeData, Person } from './makeData' -import { Table as BTable } from 'solid-bootstrap' - -import 'bootstrap/dist/css/bootstrap.min.css' - -const columns: ColumnDef[] = [ - { - header: 'Name', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'firstName', - cell: info => info.getValue(), - footer: props => props.column.id, - }, - { - accessorFn: row => row.lastName, - id: 'lastName', - cell: info => info.getValue(), - header: () => Last Name, - footer: props => props.column.id, - }, - ], - }, - { - header: 'Info', - footer: props => props.column.id, - columns: [ - { - accessorKey: 'age', - header: () => 'Age', - footer: props => props.column.id, - }, - { - header: 'More Info', - columns: [ - { - accessorKey: 'visits', - header: () => Visits, - footer: props => props.column.id, - }, - { - accessorKey: 'status', - header: 'Status', - footer: props => props.column.id, - }, - { - accessorKey: 'progress', - header: 'Profile Progress', - footer: props => props.column.id, - }, - ], - }, - ], - }, -] - -function App() { - const [data, setData] = createSignal(makeData(10)) - const rerender = () => setData(makeData(10)) - - const table = createSolidTable({ - get data() { - return data() - }, - columns, - getCoreRowModel: getCoreRowModel(), - }) - - return ( -
- - - - {headerGroup => ( - - - {header => ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} - - )} - - - )} - - - - - {row => ( - - - {cell => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - - )} - - - )} - - - - - {footerGroup => ( - - - {header => ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.footer, - header.getContext() - )} - - )} - - - )} - - - -
- -
-
- ) -} - -export default App diff --git a/examples/solid/bootstrap/src/index.css b/examples/solid/bootstrap/src/index.css deleted file mode 100644 index 43c09e0f6b..0000000000 --- a/examples/solid/bootstrap/src/index.css +++ /dev/null @@ -1,26 +0,0 @@ -html { - font-family: sans-serif; - font-size: 14px; -} - -table { - border: 1px solid lightgray; -} - -tbody { - border-bottom: 1px solid lightgray; -} - -th { - border-bottom: 1px solid lightgray; - border-right: 1px solid lightgray; - padding: 2px 4px; -} - -tfoot { - color: gray; -} - -tfoot th { - font-weight: normal; -} diff --git a/examples/solid/bootstrap/src/index.tsx b/examples/solid/bootstrap/src/index.tsx deleted file mode 100644 index b5618861da..0000000000 --- a/examples/solid/bootstrap/src/index.tsx +++ /dev/null @@ -1,7 +0,0 @@ -/* @refresh reload */ -import { render } from 'solid-js/web' - -import './index.css' -import App from './App' - -render(() => , document.getElementById('root') as HTMLElement) diff --git a/examples/solid/bootstrap/src/makeData.ts b/examples/solid/bootstrap/src/makeData.ts deleted file mode 100644 index aba0c96cd1..0000000000 --- a/examples/solid/bootstrap/src/makeData.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { faker } from '@faker-js/faker' - -export type Person = { - firstName: string - lastName: string - age: number - visits: number - progress: number - status: 'relationship' | 'complicated' | 'single' -} - -const range = (len: number) => { - const arr: number[] = [] - for (let i = 0; i < len; i++) { - arr.push(i) - } - return arr -} - -const newPerson = (): Person => { - return { - firstName: faker.person.firstName(), - lastName: faker.person.lastName(), - age: faker.number.int(40), - visits: faker.number.int(1000), - progress: faker.number.int(100), - status: faker.helpers.shuffle([ - 'relationship', - 'complicated', - 'single', - ])[0]!, - } -} - -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! - return range(len).map((d): Person => { - return newPerson() - }) - } - - return makeDataLevel() -} diff --git a/examples/solid/bootstrap/tsconfig.json b/examples/solid/bootstrap/tsconfig.json deleted file mode 100644 index 7ab027b8d2..0000000000 --- a/examples/solid/bootstrap/tsconfig.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "module": "ESNext", - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "preserve", - "jsxImportSource": "solid-js", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"] -} diff --git a/examples/solid/column-groups/package.json b/examples/solid/column-groups/package.json index 949e395816..87f7a98aa4 100644 --- a/examples/solid/column-groups/package.json +++ b/examples/solid/column-groups/package.json @@ -1,21 +1,22 @@ { - "name": "tanstack-table-example-solid-column-groups", - "version": "0.0.0", + "name": "tanstack-solid-table-example-column-groups", "description": "", "scripts": { "start": "vite", "dev": "vite", "build": "vite build", - "serve": "vite preview" + "serve": "vite preview", + "lint": "eslint ./src" }, "license": "MIT", "devDependencies": { - "typescript": "5.4.5", - "vite": "^5.3.2", - "vite-plugin-solid": "^2.10.2" + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" }, "dependencies": { - "@tanstack/solid-table": "^8.20.5", - "solid-js": "^1.8.18" + "@faker-js/faker": "^10.4.0", + "@tanstack/solid-table": "^9.0.0-alpha.45", + "solid-js": "^1.9.12" } } diff --git a/examples/solid/column-groups/src/App.tsx b/examples/solid/column-groups/src/App.tsx index 27e9526967..d1e8c8ae6b 100644 --- a/examples/solid/column-groups/src/App.tsx +++ b/examples/solid/column-groups/src/App.tsx @@ -1,74 +1,38 @@ -import { - flexRender, - getCoreRowModel, - ColumnDef, - createSolidTable, -} from '@tanstack/solid-table' -import { createSignal, For } from 'solid-js' +import { For, createSignal } from 'solid-js' +import { FlexRender, createTable, tableFeatures } from '@tanstack/solid-table' +import { makeData } from './makeData' +import type { ColumnDef } from '@tanstack/solid-table' +import type { Person } from './makeData' -type Person = { - firstName: string - lastName: string - age: number - visits: number - status: string - progress: number -} - -const defaultData: Person[] = [ - { - firstName: 'tanner', - lastName: 'linsley', - age: 24, - visits: 100, - status: 'In Relationship', - progress: 50, - }, - { - firstName: 'tandy', - lastName: 'miller', - age: 40, - visits: 40, - status: 'Single', - progress: 80, - }, - { - firstName: 'joe', - lastName: 'dirte', - age: 45, - visits: 20, - status: 'Complicated', - progress: 10, - }, -] +const _features = tableFeatures({}) -const defaultColumns: ColumnDef[] = [ +const defaultColumns: Array> = [ { header: 'Name', - footer: props => props.column.id, + footer: (props) => props.column.id, columns: [ { accessorKey: 'firstName', - cell: info => info.getValue(), - footer: props => props.column.id, + cell: (info) => info.getValue(), + footer: (props) => props.column.id, }, { - accessorFn: row => row.lastName, + accessorFn: (row) => row.lastName, id: 'lastName', - cell: info => info.getValue(), + cell: (info) => info.getValue(), header: () => Last Name, - footer: props => props.column.id, + footer: (props) => props.column.id, }, ], }, { header: 'Info', - footer: props => props.column.id, + footer: (props) => props.column.id, columns: [ { accessorKey: 'age', header: () => 'Age', - footer: props => props.column.id, + footer: (props) => props.column.id, }, { header: 'More Info', @@ -76,17 +40,17 @@ const defaultColumns: ColumnDef[] = [ { accessorKey: 'visits', header: () => Visits, - footer: props => props.column.id, + footer: (props) => props.column.id, }, { accessorKey: 'status', header: 'Status', - footer: props => props.column.id, + footer: (props) => props.column.id, }, { accessorKey: 'progress', header: 'Profile Progress', - footer: props => props.column.id, + footer: (props) => props.column.id, }, ], }, @@ -95,33 +59,36 @@ const defaultColumns: ColumnDef[] = [ ] function App() { - const [data, setData] = createSignal(defaultData) - const rerender = () => setData(defaultData) + const [data, setData] = createSignal(makeData(20)) + const refreshData = () => setData(makeData(20)) + const stressTest = () => setData(makeData(1_000)) - const table = createSolidTable({ + const table = createTable({ + debugTable: true, + _features, get data() { return data() }, columns: defaultColumns, - getCoreRowModel: getCoreRowModel(), }) return ( -
+
+
+ + +
- {headerGroup => ( + {(headerGroup) => ( - {header => ( + {(header) => ( )} @@ -131,15 +98,12 @@ function App() { - {row => ( + {(row) => ( - - {cell => ( + + {(cell) => ( )} @@ -149,17 +113,14 @@ function App() { - {footerGroup => ( + {(footerGroup) => ( - {header => ( + {(header) => ( )} @@ -168,10 +129,6 @@ function App() {
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} + {header.isPlaceholder ? null : ( + + )}
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} +
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.footer, - header.getContext() - )} + {header.isPlaceholder ? null : ( + + )}
-
-
) } diff --git a/examples/solid/column-groups/src/index.css b/examples/solid/column-groups/src/index.css index 43c09e0f6b..b9af6ded32 100644 --- a/examples/solid/column-groups/src/index.css +++ b/examples/solid/column-groups/src/index.css @@ -5,6 +5,8 @@ html { table { border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; } tbody { @@ -24,3 +26,340 @@ tfoot { tfoot th { font-weight: normal; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/column-groups/src/index.tsx b/examples/solid/column-groups/src/index.tsx index b5618861da..ea9c952177 100644 --- a/examples/solid/column-groups/src/index.tsx +++ b/examples/solid/column-groups/src/index.tsx @@ -1,6 +1,5 @@ /* @refresh reload */ import { render } from 'solid-js/web' - import './index.css' import App from './App' diff --git a/examples/solid/column-groups/src/makeData.ts b/examples/solid/column-groups/src/makeData.ts new file mode 100644 index 0000000000..b9fb014aba --- /dev/null +++ b/examples/solid/column-groups/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/solid/column-groups/tsconfig.json b/examples/solid/column-groups/tsconfig.json index 7ab027b8d2..cb996fae69 100644 --- a/examples/solid/column-groups/tsconfig.json +++ b/examples/solid/column-groups/tsconfig.json @@ -5,8 +5,6 @@ "module": "ESNext", "lib": ["ES2020", "DOM", "DOM.Iterable"], "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, @@ -14,12 +12,10 @@ "noEmit": true, "jsx": "preserve", "jsxImportSource": "solid-js", - - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["src"] + "include": ["src", "vite.config.ts", "vite.config.js"] } diff --git a/examples/solid/column-ordering/package.json b/examples/solid/column-ordering/package.json index 57fd6558ea..674f192e2f 100644 --- a/examples/solid/column-ordering/package.json +++ b/examples/solid/column-ordering/package.json @@ -1,22 +1,22 @@ { - "name": "tanstack-table-example-solid-column-ordering", - "version": "0.0.0", + "name": "tanstack-solid-table-example-column-ordering", "description": "", "scripts": { "start": "vite", "dev": "vite", "build": "vite build", - "serve": "vite preview" + "serve": "vite preview", + "lint": "eslint ./src" }, "license": "MIT", "devDependencies": { - "@faker-js/faker": "^8.4.1", - "typescript": "5.4.5", - "vite": "^5.3.2", - "vite-plugin-solid": "^2.10.2" + "@faker-js/faker": "^10.4.0", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" }, "dependencies": { - "@tanstack/solid-table": "^8.20.5", - "solid-js": "^1.8.18" + "@tanstack/solid-table": "^9.0.0-alpha.45", + "solid-js": "^1.9.12" } } diff --git a/examples/solid/column-ordering/src/App.tsx b/examples/solid/column-ordering/src/App.tsx index f09d5a9ee0..4d0c7706c0 100644 --- a/examples/solid/column-ordering/src/App.tsx +++ b/examples/solid/column-ordering/src/App.tsx @@ -1,42 +1,48 @@ -import { createSignal, For, Show } from 'solid-js' -import { makeData, Person } from './makeData' +import { For, Show, createSignal } from 'solid-js' import { faker } from '@faker-js/faker' import { - flexRender, - getCoreRowModel, - ColumnOrderState, - VisibilityState, - ColumnDef, - createSolidTable, + FlexRender, + columnOrderingFeature, + columnVisibilityFeature, + createTable, + tableFeatures, } from '@tanstack/solid-table' +import { makeData } from './makeData' +import type { Person } from './makeData' +import type { ColumnDef } from '@tanstack/solid-table' -const defaultColumns: ColumnDef[] = [ +const _features = tableFeatures({ + columnOrderingFeature, + columnVisibilityFeature, +}) + +const defaultColumns: Array> = [ { header: 'Name', - footer: props => props.column.id, + footer: (props) => props.column.id, columns: [ { accessorKey: 'firstName', - cell: info => info.getValue(), - footer: props => props.column.id, + cell: (info) => info.getValue(), + footer: (props) => props.column.id, }, { - accessorFn: row => row.lastName, + accessorFn: (row) => row.lastName, id: 'lastName', - cell: info => info.getValue(), + cell: (info) => info.getValue(), header: () => Last Name, - footer: props => props.column.id, + footer: (props) => props.column.id, }, ], }, { header: 'Info', - footer: props => props.column.id, + footer: (props) => props.column.id, columns: [ { accessorKey: 'age', header: () => 'Age', - footer: props => props.column.id, + footer: (props) => props.column.id, }, { header: 'More Info', @@ -44,17 +50,17 @@ const defaultColumns: ColumnDef[] = [ { accessorKey: 'visits', header: () => Visits, - footer: props => props.column.id, + footer: (props) => props.column.id, }, { accessorKey: 'status', header: 'Status', - footer: props => props.column.id, + footer: (props) => props.column.id, }, { accessorKey: 'progress', header: 'Profile Progress', - footer: props => props.column.id, + footer: (props) => props.column.id, }, ], }, @@ -64,40 +70,28 @@ const defaultColumns: ColumnDef[] = [ function App() { const [data, setData] = createSignal(makeData(20)) - const [columnOrder, setColumnOrder] = createSignal([]) - const [columnVisibility, setColumnVisibility] = createSignal( - {} - ) - const rerender = () => setData(() => makeData(20)) + const refreshData = () => setData(makeData(20)) + const stressTest = () => setData(makeData(1_000)) - const table = createSolidTable({ + const table = createTable({ + debugTable: true, + _features, get data() { return data() }, columns: defaultColumns, - state: { - get columnOrder() { - return columnOrder() - }, - get columnVisibility() { - return columnVisibility() - }, - }, - onColumnOrderChange: setColumnOrder, - onColumnVisibilityChange: setColumnVisibility, - getCoreRowModel: getCoreRowModel(), }) const randomizeColumns = () => { table.setColumnOrder( - faker.helpers.shuffle(table.getAllLeafColumns().map(d => d.id)) + faker.helpers.shuffle(table.getAllLeafColumns().map((d) => d.id)), ) } return ( -
-
-
+
+
+
- {column => ( -
+ {(column) => ( +
-
-
- + -
-
+
- {headerGroup => ( + {(headerGroup) => ( - {header => ( + {(header) => ( )} @@ -155,15 +155,12 @@ function App() { - {row => ( + {(row) => ( - {cell => ( + {(cell) => ( )} @@ -173,16 +170,13 @@ function App() { - {footerGroup => ( + {(footerGroup) => ( - {header => ( + {(header) => ( )} @@ -192,8 +186,8 @@ function App() {
- {flexRender( - header.column.columnDef.header, - header.getContext() - )} +
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} +
- {flexRender( - header.column.columnDef.footer, - header.getContext() - )} +
-
-
{JSON.stringify(table.getState().columnOrder, null, 2)}
+
+
{JSON.stringify(table.store.state, null, 2)}
) } diff --git a/examples/solid/column-ordering/src/index.css b/examples/solid/column-ordering/src/index.css index 93034cdd1b..efd0b26417 100644 --- a/examples/solid/column-ordering/src/index.css +++ b/examples/solid/column-ordering/src/index.css @@ -5,6 +5,8 @@ html { table { border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; } tbody { @@ -33,3 +35,340 @@ tfoot { tfoot th { font-weight: normal; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/column-ordering/src/index.tsx b/examples/solid/column-ordering/src/index.tsx index b5618861da..ea9c952177 100644 --- a/examples/solid/column-ordering/src/index.tsx +++ b/examples/solid/column-ordering/src/index.tsx @@ -1,6 +1,5 @@ /* @refresh reload */ import { render } from 'solid-js/web' - import './index.css' import App from './App' diff --git a/examples/solid/column-ordering/src/makeData.ts b/examples/solid/column-ordering/src/makeData.ts index 331dd1eb19..b9fb014aba 100644 --- a/examples/solid/column-ordering/src/makeData.ts +++ b/examples/solid/column-ordering/src/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,13 +29,13 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] return range(len).map((d): Person => { return { ...newPerson(), diff --git a/examples/solid/column-ordering/tsconfig.json b/examples/solid/column-ordering/tsconfig.json index 7ab027b8d2..cb996fae69 100644 --- a/examples/solid/column-ordering/tsconfig.json +++ b/examples/solid/column-ordering/tsconfig.json @@ -5,8 +5,6 @@ "module": "ESNext", "lib": ["ES2020", "DOM", "DOM.Iterable"], "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, @@ -14,12 +12,10 @@ "noEmit": true, "jsx": "preserve", "jsxImportSource": "solid-js", - - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["src"] + "include": ["src", "vite.config.ts", "vite.config.js"] } diff --git a/examples/solid/column-pinning-split/index.html b/examples/solid/column-pinning-split/index.html new file mode 100644 index 0000000000..4d3220bdd2 --- /dev/null +++ b/examples/solid/column-pinning-split/index.html @@ -0,0 +1,13 @@ + + + + + + Solid App + + + +
+ + + diff --git a/examples/solid/column-pinning-split/package.json b/examples/solid/column-pinning-split/package.json new file mode 100644 index 0000000000..865205b402 --- /dev/null +++ b/examples/solid/column-pinning-split/package.json @@ -0,0 +1,22 @@ +{ + "name": "tanstack-solid-table-example-column-pinning-split", + "description": "", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ./src" + }, + "license": "MIT", + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" + }, + "dependencies": { + "@tanstack/solid-table": "^9.0.0-alpha.45", + "solid-js": "^1.9.12" + } +} diff --git a/examples/solid/column-pinning-split/src/App.tsx b/examples/solid/column-pinning-split/src/App.tsx new file mode 100644 index 0000000000..e197b54036 --- /dev/null +++ b/examples/solid/column-pinning-split/src/App.tsx @@ -0,0 +1,293 @@ +import { faker } from '@faker-js/faker' +import { + columnOrderingFeature, + columnPinningFeature, + columnVisibilityFeature, + createTable, + tableFeatures, +} from '@tanstack/solid-table' +import { For, createSignal } from 'solid-js' +import { makeData } from './makeData' +import type { ColumnDef } from '@tanstack/solid-table' +import type { Person } from './makeData' + +const _features = tableFeatures({ + columnVisibilityFeature, + columnPinningFeature, + columnOrderingFeature, +}) + +const defaultColumns: Array> = [ + { + header: 'Name', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'firstName', + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }, + ], + }, + { + header: 'Info', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'age', + header: () => 'Age', + footer: (props) => props.column.id, + }, + { + header: 'More Info', + columns: [ + { + accessorKey: 'visits', + header: () => Visits, + footer: (props) => props.column.id, + }, + { + accessorKey: 'status', + header: 'Status', + footer: (props) => props.column.id, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + footer: (props) => props.column.id, + }, + ], + }, + ], + }, +] + +function App() { + const [data, setData] = createSignal(makeData(1_000)) + const columns = defaultColumns + const refreshData = () => setData(makeData(1_000)) + const stressTest = () => setData(makeData(500_000)) + + const table = createTable({ + _features, + _rowModels: {}, + columns, + get data() { + return data() + }, + debugTable: true, + debugHeaders: true, + debugColumns: true, + }) + + const randomizeColumns = () => { + table.setColumnOrder( + faker.helpers.shuffle(table.getAllLeafColumns().map((d) => d.id)), + ) + } + + const PinButtons = (props: { + column: ReturnType[0] + }) => ( +
+ {props.column.getIsPinned() !== 'left' ? ( + + ) : null} + {props.column.getIsPinned() ? ( + + ) : null} + {props.column.getIsPinned() !== 'right' ? ( + + ) : null} +
+ ) + + return ( +
+
+
+ +
+ + {(column) => ( +
+ +
+ )} +
+
+
+
+ + + +
+
+

+ This example takes advantage of the "splitting" APIs. (APIs that have + "left", "center", and "right" modifiers) +

+
+ + + + {(headerGroup) => ( + + + {(header) => ( + + )} + + + )} + + + + + {(row) => ( + + + {(cell) => ( + + )} + + + )} + + +
+
+ {header.isPlaceholder ? null : ( + + )} +
+ {!header.isPlaceholder && header.column.getCanPin() && ( + + )} +
+ +
+ + + + {(headerGroup) => ( + + + {(header) => ( + + )} + + + )} + + + + + {(row) => ( + + + {(cell) => ( + + )} + + + )} + + +
+
+ {header.isPlaceholder ? null : ( + + )} +
+ {!header.isPlaceholder && header.column.getCanPin() && ( + + )} +
+ +
+ + + + {(headerGroup) => ( + + + {(header) => ( + + )} + + + )} + + + + + {(row) => ( + + + {(cell) => ( + + )} + + + )} + + +
+
+ {header.isPlaceholder ? null : ( + + )} +
+ {!header.isPlaceholder && header.column.getCanPin() && ( + + )} +
+ +
+
+
{JSON.stringify(table.store.state, null, 2)}
+
+ ) +} + +export default App diff --git a/examples/solid/column-pinning-split/src/index.css b/examples/solid/column-pinning-split/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/solid/column-pinning-split/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/column-pinning-split/src/index.tsx b/examples/solid/column-pinning-split/src/index.tsx new file mode 100644 index 0000000000..ea9c952177 --- /dev/null +++ b/examples/solid/column-pinning-split/src/index.tsx @@ -0,0 +1,6 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import './index.css' +import App from './App' + +render(() => , document.getElementById('root') as HTMLElement) diff --git a/examples/solid/column-pinning-split/src/makeData.ts b/examples/solid/column-pinning-split/src/makeData.ts new file mode 100644 index 0000000000..d274a17f45 --- /dev/null +++ b/examples/solid/column-pinning-split/src/makeData.ts @@ -0,0 +1,47 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + return makeDataLevel() +} diff --git a/examples/solid/column-pinning-split/tsconfig.json b/examples/solid/column-pinning-split/tsconfig.json new file mode 100644 index 0000000000..cb996fae69 --- /dev/null +++ b/examples/solid/column-pinning-split/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts", "vite.config.js"] +} diff --git a/examples/solid/column-pinning-split/vite.config.ts b/examples/solid/column-pinning-split/vite.config.ts new file mode 100644 index 0000000000..b80a99b402 --- /dev/null +++ b/examples/solid/column-pinning-split/vite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite' +import solidPlugin from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solidPlugin()], + build: { + target: 'esnext', + }, +}) diff --git a/examples/solid/column-pinning-sticky/index.html b/examples/solid/column-pinning-sticky/index.html new file mode 100644 index 0000000000..4d3220bdd2 --- /dev/null +++ b/examples/solid/column-pinning-sticky/index.html @@ -0,0 +1,13 @@ + + + + + + Solid App + + + +
+ + + diff --git a/examples/solid/column-pinning-sticky/package.json b/examples/solid/column-pinning-sticky/package.json new file mode 100644 index 0000000000..a1ed59c049 --- /dev/null +++ b/examples/solid/column-pinning-sticky/package.json @@ -0,0 +1,22 @@ +{ + "name": "tanstack-solid-table-example-column-pinning-sticky", + "description": "", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ./src" + }, + "license": "MIT", + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" + }, + "dependencies": { + "@tanstack/solid-table": "^9.0.0-alpha.45", + "solid-js": "^1.9.12" + } +} diff --git a/examples/solid/column-pinning-sticky/src/App.tsx b/examples/solid/column-pinning-sticky/src/App.tsx new file mode 100644 index 0000000000..39d5bd19b8 --- /dev/null +++ b/examples/solid/column-pinning-sticky/src/App.tsx @@ -0,0 +1,262 @@ +import { faker } from '@faker-js/faker' +import { + columnOrderingFeature, + columnPinningFeature, + columnResizingFeature, + columnSizingFeature, + columnVisibilityFeature, + createTable, + tableFeatures, +} from '@tanstack/solid-table' +import { For, createSignal } from 'solid-js' +import { makeData } from './makeData' +import type { Column } from '@tanstack/solid-table' +import type { JSX } from 'solid-js' +import type { Person } from './makeData' + +const _features = tableFeatures({ + columnOrderingFeature, + columnPinningFeature, + columnResizingFeature, + columnSizingFeature, + columnVisibilityFeature, +}) + +const getCommonPinningStyles = ( + column: Column, +): JSX.CSSProperties => { + const isPinned = column.getIsPinned() + const isLastLeftPinnedColumn = + isPinned === 'left' && column.getIsLastColumn('left') + const isFirstRightPinnedColumn = + isPinned === 'right' && column.getIsFirstColumn('right') + + return { + 'box-shadow': isLastLeftPinnedColumn + ? '-4px 0 4px -4px gray inset' + : isFirstRightPinnedColumn + ? '4px 0 4px -4px gray inset' + : undefined, + left: isPinned === 'left' ? `${column.getStart('left')}px` : undefined, + right: isPinned === 'right' ? `${column.getAfter('right')}px` : undefined, + opacity: isPinned ? 0.95 : 1, + position: isPinned ? 'sticky' : 'relative', + width: `${column.getSize()}px`, + 'z-index': isPinned ? 1 : 0, + } +} + +const defaultColumns = [ + { + accessorKey: 'firstName', + id: 'firstName', + header: 'First Name', + cell: (info: any) => info.getValue(), + footer: (props: any) => props.column.id, + size: 180, + }, + { + accessorFn: (row: Person) => row.lastName, + id: 'lastName', + cell: (info: any) => info.getValue(), + header: () => Last Name, + footer: (props: any) => props.column.id, + size: 180, + }, + { + accessorKey: 'age', + id: 'age', + header: 'Age', + footer: (props: any) => props.column.id, + size: 180, + }, + { + accessorKey: 'visits', + id: 'visits', + header: 'Visits', + footer: (props: any) => props.column.id, + size: 180, + }, + { + accessorKey: 'status', + id: 'status', + header: 'Status', + footer: (props: any) => props.column.id, + size: 180, + }, + { + accessorKey: 'progress', + id: 'progress', + header: 'Profile Progress', + footer: (props: any) => props.column.id, + size: 180, + }, +] + +function App() { + const [data, setData] = createSignal(makeData(20)) + const refreshData = () => setData(makeData(20)) + const stressTest = () => setData(makeData(1_000)) + + const table = createTable( + { + _features, + _rowModels: {}, + columns: defaultColumns, + get data() { + return data() + }, + debugTable: true, + debugHeaders: true, + debugColumns: true, + columnResizeMode: 'onChange', + }, + (state) => state, + ) + + const randomizeColumns = () => { + table.setColumnOrder( + faker.helpers.shuffle(table.getAllLeafColumns().map((d) => d.id)), + ) + } + + return ( +
+
+
+ +
+ + {(column) => ( +
+ +
+ )} +
+
+
+
+ + + +
+
+
+ + + + {(headerGroup) => ( + + + {(header) => { + const { column } = header + return ( + + ) + }} + + + )} + + + + + {(row) => ( + + + {(cell) => { + const { column } = cell + return ( + + ) + }} + + + )} + + +
+
+ {header.isPlaceholder ? null : ( + <> + {' '} + + )} + {column.getIndex(column.getIsPinned() || 'center')} +
+ {!header.isPlaceholder && + header.column.getCanPin() && ( +
+ {header.column.getIsPinned() !== 'left' ? ( + + ) : null} + {header.column.getIsPinned() ? ( + + ) : null} + {header.column.getIsPinned() !== 'right' ? ( + + ) : null} +
+ )} +
header.column.resetSize()} + onMouseDown={header.getResizeHandler()} + onTouchStart={header.getResizeHandler()} + class={`resizer ${ + header.column.getIsResizing() ? 'isResizing' : '' + }`} + /> +
+ +
+
+
{JSON.stringify(table.store.state, null, 2)}
+
+ ) +} + +export default App diff --git a/examples/solid/column-pinning-sticky/src/index.css b/examples/solid/column-pinning-sticky/src/index.css new file mode 100644 index 0000000000..cf254482e6 --- /dev/null +++ b/examples/solid/column-pinning-sticky/src/index.css @@ -0,0 +1,388 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +.table-container { + border: 1px solid lightgray; + overflow-x: scroll; + width: 100%; + max-width: 960px; + position: relative; + border-collapse: collapse; + border-spacing: 0; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +th { + background-color: lightgray; + border-bottom: 1px solid lightgray; + font-weight: bold; + height: 30px; + padding: 2px 4px; + position: relative; + text-align: center; +} + +td { + background-color: white; + padding: 2px 4px; +} + +.resizer { + background: rgba(0, 0, 0, 0.5); + cursor: col-resize; + height: 100%; + position: absolute; + right: 0; + top: 0; + touch-action: none; + user-select: none; + width: 5px; +} + +.resizer.isResizing { + background: blue; + opacity: 1; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/column-pinning-sticky/src/index.tsx b/examples/solid/column-pinning-sticky/src/index.tsx new file mode 100644 index 0000000000..ea9c952177 --- /dev/null +++ b/examples/solid/column-pinning-sticky/src/index.tsx @@ -0,0 +1,6 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import './index.css' +import App from './App' + +render(() => , document.getElementById('root') as HTMLElement) diff --git a/examples/solid/column-pinning-sticky/src/makeData.ts b/examples/solid/column-pinning-sticky/src/makeData.ts new file mode 100644 index 0000000000..d274a17f45 --- /dev/null +++ b/examples/solid/column-pinning-sticky/src/makeData.ts @@ -0,0 +1,47 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + return makeDataLevel() +} diff --git a/examples/solid/column-pinning-sticky/tsconfig.json b/examples/solid/column-pinning-sticky/tsconfig.json new file mode 100644 index 0000000000..cb996fae69 --- /dev/null +++ b/examples/solid/column-pinning-sticky/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts", "vite.config.js"] +} diff --git a/examples/solid/column-pinning-sticky/vite.config.ts b/examples/solid/column-pinning-sticky/vite.config.ts new file mode 100644 index 0000000000..b80a99b402 --- /dev/null +++ b/examples/solid/column-pinning-sticky/vite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite' +import solidPlugin from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solidPlugin()], + build: { + target: 'esnext', + }, +}) diff --git a/examples/solid/column-pinning/index.html b/examples/solid/column-pinning/index.html new file mode 100644 index 0000000000..938a04d252 --- /dev/null +++ b/examples/solid/column-pinning/index.html @@ -0,0 +1,14 @@ + + + + + + + Solid App + + + +
+ + + diff --git a/examples/solid/column-pinning/package.json b/examples/solid/column-pinning/package.json new file mode 100644 index 0000000000..c02d85dd9b --- /dev/null +++ b/examples/solid/column-pinning/package.json @@ -0,0 +1,22 @@ +{ + "name": "tanstack-solid-table-example-column-pinning", + "description": "", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ./src" + }, + "license": "MIT", + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" + }, + "dependencies": { + "@tanstack/solid-table": "^9.0.0-alpha.45", + "solid-js": "^1.9.12" + } +} diff --git a/examples/solid/column-pinning/src/App.tsx b/examples/solid/column-pinning/src/App.tsx new file mode 100644 index 0000000000..3acb3e4310 --- /dev/null +++ b/examples/solid/column-pinning/src/App.tsx @@ -0,0 +1,213 @@ +import { faker } from '@faker-js/faker' +import { + columnOrderingFeature, + columnPinningFeature, + columnVisibilityFeature, + createTableHook, +} from '@tanstack/solid-table' +import { For, createSignal } from 'solid-js' +import { makeData } from './makeData' +import type { Person } from './makeData' + +const { createAppTable, createAppColumnHelper } = createTableHook({ + _features: { + columnVisibilityFeature, + columnPinningFeature, + columnOrderingFeature, + }, + _rowModels: {}, + debugTable: true, + debugHeaders: true, + debugColumns: true, +}) + +const columnHelper = createAppColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.group({ + header: 'Name', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }), + ]), + }), + columnHelper.group({ + header: 'Info', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + }), + columnHelper.group({ + header: 'More Info', + columns: columnHelper.columns([ + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), + ]), + }), + ]), + }), +]) + +function App() { + const [data, setData] = createSignal(makeData(1_000)) + const refreshData = () => setData(makeData(1_000)) + const stressTest = () => setData(makeData(500_000)) + + const table = createAppTable({ + debugTable: true, + columns, + get data() { + return data() + }, + }) + + const randomizeColumns = () => { + table.setColumnOrder( + faker.helpers.shuffle(table.getAllLeafColumns().map((d) => d.id)), + ) + } + + return ( +
+
+
+ +
+ + {(column) => ( +
+ +
+ )} +
+
+
+
+ + + +
+
+

+ This example using the non-split APIs. Columns are just reordered within + 1 table instead of being split into 3 different tables. +

+
+ + + + {(headerGroup) => ( + + + {(header) => ( + + )} + + + )} + + + + + {(row) => ( + + + {(cell) => ( + + )} + + + )} + + +
+
+ {header.isPlaceholder ? null : ( + + )} +
+ {!header.isPlaceholder && header.column.getCanPin() && ( +
+ {header.column.getIsPinned() !== 'left' ? ( + + ) : null} + {header.column.getIsPinned() ? ( + + ) : null} + {header.column.getIsPinned() !== 'right' ? ( + + ) : null} +
+ )} +
+ +
+
+
{JSON.stringify(table.store.state, null, 2)}
+
+ ) +} + +export default App diff --git a/examples/solid/column-pinning/src/index.css b/examples/solid/column-pinning/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/solid/column-pinning/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/column-pinning/src/index.tsx b/examples/solid/column-pinning/src/index.tsx new file mode 100644 index 0000000000..ea9c952177 --- /dev/null +++ b/examples/solid/column-pinning/src/index.tsx @@ -0,0 +1,6 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import './index.css' +import App from './App' + +render(() => , document.getElementById('root') as HTMLElement) diff --git a/examples/solid/column-pinning/src/makeData.ts b/examples/solid/column-pinning/src/makeData.ts new file mode 100644 index 0000000000..d274a17f45 --- /dev/null +++ b/examples/solid/column-pinning/src/makeData.ts @@ -0,0 +1,47 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + return makeDataLevel() +} diff --git a/examples/solid/column-pinning/tsconfig.json b/examples/solid/column-pinning/tsconfig.json new file mode 100644 index 0000000000..cb996fae69 --- /dev/null +++ b/examples/solid/column-pinning/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts", "vite.config.js"] +} diff --git a/examples/solid/column-pinning/vite.config.ts b/examples/solid/column-pinning/vite.config.ts new file mode 100644 index 0000000000..b80a99b402 --- /dev/null +++ b/examples/solid/column-pinning/vite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite' +import solidPlugin from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solidPlugin()], + build: { + target: 'esnext', + }, +}) diff --git a/examples/solid/column-resizing-performant/index.html b/examples/solid/column-resizing-performant/index.html new file mode 100644 index 0000000000..4d3220bdd2 --- /dev/null +++ b/examples/solid/column-resizing-performant/index.html @@ -0,0 +1,13 @@ + + + + + + Solid App + + + +
+ + + diff --git a/examples/solid/column-resizing-performant/package.json b/examples/solid/column-resizing-performant/package.json new file mode 100644 index 0000000000..cbc5513a8e --- /dev/null +++ b/examples/solid/column-resizing-performant/package.json @@ -0,0 +1,22 @@ +{ + "name": "tanstack-solid-table-example-column-resizing-performant", + "description": "", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ./src" + }, + "license": "MIT", + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" + }, + "dependencies": { + "@tanstack/solid-table": "^9.0.0-alpha.45", + "solid-js": "^1.9.12" + } +} diff --git a/examples/solid/column-resizing-performant/src/App.tsx b/examples/solid/column-resizing-performant/src/App.tsx new file mode 100644 index 0000000000..f54ca51af4 --- /dev/null +++ b/examples/solid/column-resizing-performant/src/App.tsx @@ -0,0 +1,186 @@ +import { + columnResizingFeature, + columnSizingFeature, + createColumnHelper, + createTable, + tableFeatures, +} from '@tanstack/solid-table' +import { For, createMemo, createSignal } from 'solid-js' +import { makeData } from './makeData' +import type { Table as SolidTableType } from '@tanstack/solid-table' +import type { Person } from './makeData' + +const _features = tableFeatures({ columnSizingFeature, columnResizingFeature }) + +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.group({ + header: 'Name', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }), + ]), + }), + columnHelper.group({ + header: 'Info', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + }), + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), + ]), + }), +]) + +function App() { + const [data, setData] = createSignal(makeData(200)) + const refreshData = () => setData(makeData(200)) + const stressTest = () => setData(makeData(2_000)) + + const table = createTable( + { + _features, + _rowModels: {}, + columns, + get data() { + return data() + }, + defaultColumn: { minSize: 60, maxSize: 800 }, + columnResizeMode: 'onChange', + debugTable: true, + debugHeaders: true, + debugColumns: true, + }, + (state) => ({ + columnSizing: state.columnSizing, + columnResizing: state.columnResizing, + }), + ) + + const columnSizeVars = createMemo(() => { + const headers = table.getFlatHeaders() + const colSizes: Record = {} + for (const header of headers) { + colSizes[`--header-${header.id}-size`] = header.getSize() + colSizes[`--col-${header.column.id}-size`] = header.column.getSize() + } + return colSizes + }) + + return ( +
+
+ + +
+ + This example has artificially slow cell renders to simulate complex + usage + +
+
+        {JSON.stringify(table.store.state, null, 2)}
+      
+
({data().length.toLocaleString()} rows) +
+
+
+ + {(headerGroup) => ( +
+ + {(header) => ( +
+ {header.isPlaceholder ? null : ( + + )} +
header.column.resetSize()} + onMouseDown={header.getResizeHandler()} + onTouchStart={header.getResizeHandler()} + class={`resizer ${header.column.getIsResizing() ? 'isResizing' : ''}`} + /> +
+ )} + +
+ )} +
+
+ +
+
+
+ ) +} + +function TableBody({ + table, +}: { + table: SolidTableType +}) { + return ( +
+ + {(row) => ( +
+ + {(cell) => { + // simulate expensive render + for (const _ of Array.from({ length: 10000 })) { + Math.random() + } + return ( +
+ {cell.renderValue()} +
+ ) + }} +
+
+ )} +
+
+ ) +} + +export default App diff --git a/examples/solid/column-resizing-performant/src/index.css b/examples/solid/column-resizing-performant/src/index.css new file mode 100644 index 0000000000..78a395f530 --- /dev/null +++ b/examples/solid/column-resizing-performant/src/index.css @@ -0,0 +1,411 @@ +* { + box-sizing: border-box; +} + +html { + font-family: sans-serif; + font-size: 14px; +} + +table, +.divTable { + border: 1px solid lightgray; + width: fit-content; + border-collapse: collapse; + border-spacing: 0; +} + +.tr { + display: flex; +} + +tr, +.tr { + width: fit-content; + height: 30px; +} + +th, +.th, +td, +.td { + box-shadow: inset 0 0 0 1px lightgray; + padding: 0.25rem; +} + +th, +.th { + padding: 2px 4px; + position: relative; + font-weight: bold; + text-align: center; + height: 30px; +} + +td, +.td { + height: 30px; +} + +.resizer { + position: absolute; + top: 0; + height: 100%; + width: 5px; + background: rgba(0, 0, 0, 0.5); + cursor: col-resize; + user-select: none; + touch-action: none; +} + +.resizer.isResizing { + background: blue; + opacity: 1; +} + +@media (hover: hover) { + .resizer { + opacity: 0; + } + + *:hover > .resizer { + opacity: 1; + } +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/column-resizing-performant/src/index.tsx b/examples/solid/column-resizing-performant/src/index.tsx new file mode 100644 index 0000000000..ea9c952177 --- /dev/null +++ b/examples/solid/column-resizing-performant/src/index.tsx @@ -0,0 +1,6 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import './index.css' +import App from './App' + +render(() => , document.getElementById('root') as HTMLElement) diff --git a/examples/solid/column-resizing-performant/src/makeData.ts b/examples/solid/column-resizing-performant/src/makeData.ts new file mode 100644 index 0000000000..d63c724b74 --- /dev/null +++ b/examples/solid/column-resizing-performant/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/solid/column-resizing-performant/tsconfig.json b/examples/solid/column-resizing-performant/tsconfig.json new file mode 100644 index 0000000000..cb996fae69 --- /dev/null +++ b/examples/solid/column-resizing-performant/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts", "vite.config.js"] +} diff --git a/examples/solid/column-resizing-performant/vite.config.ts b/examples/solid/column-resizing-performant/vite.config.ts new file mode 100644 index 0000000000..b80a99b402 --- /dev/null +++ b/examples/solid/column-resizing-performant/vite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite' +import solidPlugin from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solidPlugin()], + build: { + target: 'esnext', + }, +}) diff --git a/examples/solid/column-resizing/index.html b/examples/solid/column-resizing/index.html new file mode 100644 index 0000000000..4d3220bdd2 --- /dev/null +++ b/examples/solid/column-resizing/index.html @@ -0,0 +1,13 @@ + + + + + + Solid App + + + +
+ + + diff --git a/examples/solid/column-resizing/package.json b/examples/solid/column-resizing/package.json new file mode 100644 index 0000000000..60679ef26e --- /dev/null +++ b/examples/solid/column-resizing/package.json @@ -0,0 +1,22 @@ +{ + "name": "tanstack-solid-table-example-column-resizing", + "description": "", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ./src" + }, + "license": "MIT", + "devDependencies": { + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/solid-table": "^9.0.0-alpha.45", + "solid-js": "^1.9.12" + } +} diff --git a/examples/solid/column-resizing/src/App.tsx b/examples/solid/column-resizing/src/App.tsx new file mode 100644 index 0000000000..94a92857ca --- /dev/null +++ b/examples/solid/column-resizing/src/App.tsx @@ -0,0 +1,301 @@ +import { + columnResizingFeature, + columnSizingFeature, + createColumnHelper, + createTable, + tableFeatures, +} from '@tanstack/solid-table' +import { For, createSignal } from 'solid-js' +import { makeData } from './makeData' +import type { + ColumnResizeDirection, + ColumnResizeMode, +} from '@tanstack/solid-table' +import type { Person } from './makeData' + +const _features = tableFeatures({ columnResizingFeature, columnSizingFeature }) + +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.group({ + header: 'Name', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }), + ]), + }), + columnHelper.group({ + header: 'Info', + footer: (props) => props.column.id, + columns: columnHelper.columns([ + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + }), + columnHelper.group({ + header: 'More Info', + columns: columnHelper.columns([ + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), + ]), + }), + ]), + }), +]) + +function App() { + const [data, setData] = createSignal(makeData(10)) + const refreshData = () => setData(makeData(10)) + const stressTest = () => setData(makeData(100)) + const [columnResizeMode, setColumnResizeMode] = + createSignal('onChange') + const [columnResizeDirection, setColumnResizeDirection] = + createSignal('ltr') + + const table = createTable( + { + _features, + _rowModels: {}, + columns, + get data() { + return data() + }, + get columnResizeMode() { + return columnResizeMode() + }, + get columnResizeDirection() { + return columnResizeDirection() + }, + debugTable: true, + debugHeaders: true, + debugColumns: true, + }, + (state) => state, + ) + + const resizerTransform = ( + header: ReturnType[number]['headers'][number], + ) => { + if (columnResizeMode() === 'onEnd' && header.column.getIsResizing()) { + const delta = table.store.state.columnResizing.deltaOffset ?? 0 + const dir = table.options.columnResizeDirection === 'rtl' ? -1 : 1 + return `translateX(${dir * delta}px)` + } + return '' + } + + return ( +
+
+ + +
+ + +
+
+
{''} +
+
+ + + {(headerGroup) => ( + + + {(header) => ( + + )} + + + )} + + + + + {(row) => ( + + + {(cell) => ( + + )} + + + )} + + +
+ {header.isPlaceholder ? null : ( + + )} +
header.column.resetSize()} + onMouseDown={header.getResizeHandler()} + onTouchStart={header.getResizeHandler()} + class={`resizer ${table.options.columnResizeDirection} ${header.column.getIsResizing() ? 'isResizing' : ''}`} + style={{ transform: resizerTransform(header) }} + /> +
+ +
+
+
+
{'
(relative)'}
+
+
+
+ + {(headerGroup) => ( +
+ + {(header) => ( +
+ {header.isPlaceholder ? null : ( + + )} +
header.column.resetSize()} + onMouseDown={header.getResizeHandler()} + onTouchStart={header.getResizeHandler()} + class={`resizer ${table.options.columnResizeDirection} ${header.column.getIsResizing() ? 'isResizing' : ''}`} + style={{ transform: resizerTransform(header) }} + /> +
+ )} + +
+ )} +
+
+
+ + {(row) => ( +
+ + {(cell) => ( +
+ +
+ )} +
+
+ )} +
+
+
+
+
+
{'
(absolute positioning)'}
+
+
+
+ + {(headerGroup) => ( +
+ + {(header) => ( +
+ {header.isPlaceholder ? null : ( + + )} +
header.column.resetSize()} + onMouseDown={header.getResizeHandler()} + onTouchStart={header.getResizeHandler()} + class={`resizer ${table.options.columnResizeDirection} ${header.column.getIsResizing() ? 'isResizing' : ''}`} + style={{ transform: resizerTransform(header) }} + /> +
+ )} + +
+ )} +
+
+
+ + {(row) => ( +
+ + {(cell) => ( +
+ +
+ )} +
+
+ )} +
+
+
+
+
+
+
{JSON.stringify(table.store.state, null, 2)}
+
+ ) +} + +export default App diff --git a/examples/solid/column-resizing/src/index.css b/examples/solid/column-resizing/src/index.css new file mode 100644 index 0000000000..d7f648b224 --- /dev/null +++ b/examples/solid/column-resizing/src/index.css @@ -0,0 +1,419 @@ +* { + box-sizing: border-box; +} + +html { + font-family: sans-serif; + font-size: 14px; +} + +table, +.divTable { + border: 1px solid lightgray; + width: fit-content; + border-collapse: collapse; + border-spacing: 0; +} + +.tr { + display: flex; +} + +tr, +.tr { + width: fit-content; + height: 30px; +} + +th, +.th, +td, +.td { + box-shadow: inset 0 0 0 1px lightgray; + padding: 0.25rem; +} + +th, +.th { + padding: 2px 4px; + position: relative; + font-weight: bold; + text-align: center; + height: 30px; +} + +td, +.td { + height: 30px; +} + +.resizer { + position: absolute; + top: 0; + height: 100%; + width: 5px; + background: rgba(0, 0, 0, 0.5); + cursor: col-resize; + user-select: none; + touch-action: none; +} + +.resizer.ltr { + right: 0; +} + +.resizer.rtl { + left: 0; +} + +.resizer.isResizing { + background: blue; + opacity: 1; +} + +@media (hover: hover) { + .resizer { + opacity: 0; + } + + *:hover > .resizer { + opacity: 1; + } +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/column-resizing/src/index.tsx b/examples/solid/column-resizing/src/index.tsx new file mode 100644 index 0000000000..ea9c952177 --- /dev/null +++ b/examples/solid/column-resizing/src/index.tsx @@ -0,0 +1,6 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import './index.css' +import App from './App' + +render(() => , document.getElementById('root') as HTMLElement) diff --git a/examples/solid/column-resizing/src/makeData.ts b/examples/solid/column-resizing/src/makeData.ts new file mode 100644 index 0000000000..b9fb014aba --- /dev/null +++ b/examples/solid/column-resizing/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/solid/column-resizing/tsconfig.json b/examples/solid/column-resizing/tsconfig.json new file mode 100644 index 0000000000..cb996fae69 --- /dev/null +++ b/examples/solid/column-resizing/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts", "vite.config.js"] +} diff --git a/examples/solid/column-resizing/vite.config.ts b/examples/solid/column-resizing/vite.config.ts new file mode 100644 index 0000000000..b80a99b402 --- /dev/null +++ b/examples/solid/column-resizing/vite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite' +import solidPlugin from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solidPlugin()], + build: { + target: 'esnext', + }, +}) diff --git a/examples/solid/column-sizing/index.html b/examples/solid/column-sizing/index.html new file mode 100644 index 0000000000..938a04d252 --- /dev/null +++ b/examples/solid/column-sizing/index.html @@ -0,0 +1,14 @@ + + + + + + + Solid App + + + +
+ + + diff --git a/examples/solid/column-sizing/package.json b/examples/solid/column-sizing/package.json new file mode 100644 index 0000000000..c1693222ac --- /dev/null +++ b/examples/solid/column-sizing/package.json @@ -0,0 +1,22 @@ +{ + "name": "tanstack-solid-table-example-column-sizing", + "description": "", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ./src" + }, + "license": "MIT", + "devDependencies": { + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/solid-table": "^9.0.0-alpha.45", + "solid-js": "^1.9.12" + } +} diff --git a/examples/solid/column-sizing/src/App.tsx b/examples/solid/column-sizing/src/App.tsx new file mode 100644 index 0000000000..fbc329c745 --- /dev/null +++ b/examples/solid/column-sizing/src/App.tsx @@ -0,0 +1,252 @@ +import { + columnSizingFeature, + createColumnHelper, + createTable, + tableFeatures, +} from '@tanstack/solid-table' +import { For, createSignal } from 'solid-js' +import { makeData } from './makeData' +import type { Person } from './makeData' + +const _features = tableFeatures({ columnSizingFeature }) + +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + size: 120, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + size: 120, + }), + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + size: 100, + }), + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + size: 80, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + size: 200, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + size: 200, + }), +]) + +function App() { + const [data, setData] = createSignal(makeData(20)) + const refreshData = () => setData(makeData(20)) + const stressTest = () => setData(makeData(1_000)) + + const table = createTable( + { + _features, + _rowModels: {}, + columns, + get data() { + return data() + }, + debugTable: true, + debugHeaders: true, + debugColumns: true, + }, + (state) => state, + ) + + return ( +
+
+ + +
+
+
+
{'Initial Column Sizes'}
+
+ + {(column) => ( +
+ +
+ )} +
+
+
+
+
{''} +
+
+ + + {(headerGroup) => ( + + + {(header) => ( + + )} + + + )} + + + + + {(row) => ( + + + {(cell) => ( + + )} + + + )} + + +
+ {header.isPlaceholder ? null : ( + + )} +
+
+ +
+
+
+
{'
(relative)'}
+
+
+
+ + {(headerGroup) => ( +
+ + {(header) => ( +
+ {header.isPlaceholder ? null : ( + + )} +
+
+ )} + +
+ )} +
+
+
+ + {(row) => ( +
+ + {(cell) => ( +
+ +
+ )} +
+
+ )} +
+
+
+
+
+
{'
(absolute positioning)'}
+
+
+
+ + {(headerGroup) => ( +
+ + {(header) => ( +
+ {header.isPlaceholder ? null : ( + + )} +
+
+ )} + +
+ )} +
+
+
+ + {(row) => ( +
+ + {(cell) => ( +
+ +
+ )} +
+
+ )} +
+
+
+
+
+
{JSON.stringify(table.store.state, null, 2)}
+
+ ) +} + +export default App diff --git a/examples/solid/column-sizing/src/index.css b/examples/solid/column-sizing/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/solid/column-sizing/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/column-sizing/src/index.tsx b/examples/solid/column-sizing/src/index.tsx new file mode 100644 index 0000000000..ea9c952177 --- /dev/null +++ b/examples/solid/column-sizing/src/index.tsx @@ -0,0 +1,6 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import './index.css' +import App from './App' + +render(() => , document.getElementById('root') as HTMLElement) diff --git a/examples/solid/column-sizing/src/makeData.ts b/examples/solid/column-sizing/src/makeData.ts new file mode 100644 index 0000000000..b9fb014aba --- /dev/null +++ b/examples/solid/column-sizing/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/solid/column-sizing/tsconfig.json b/examples/solid/column-sizing/tsconfig.json new file mode 100644 index 0000000000..cb996fae69 --- /dev/null +++ b/examples/solid/column-sizing/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts", "vite.config.js"] +} diff --git a/examples/solid/column-sizing/vite.config.ts b/examples/solid/column-sizing/vite.config.ts new file mode 100644 index 0000000000..b80a99b402 --- /dev/null +++ b/examples/solid/column-sizing/vite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite' +import solidPlugin from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solidPlugin()], + build: { + target: 'esnext', + }, +}) diff --git a/examples/solid/column-visibility/package.json b/examples/solid/column-visibility/package.json index 5ef2480071..118cc3eec6 100644 --- a/examples/solid/column-visibility/package.json +++ b/examples/solid/column-visibility/package.json @@ -1,21 +1,22 @@ { - "name": "tanstack-table-example-solid-column-visibility", - "version": "0.0.0", + "name": "tanstack-solid-table-example-column-visibility", "description": "", "scripts": { "start": "vite", "dev": "vite", "build": "vite build", - "serve": "vite preview" + "serve": "vite preview", + "lint": "eslint ./src" }, "license": "MIT", "devDependencies": { - "typescript": "5.4.5", - "vite": "^5.3.2", - "vite-plugin-solid": "^2.10.2" + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" }, "dependencies": { - "@tanstack/solid-table": "^8.20.5", - "solid-js": "^1.8.18" + "@faker-js/faker": "^10.4.0", + "@tanstack/solid-table": "^9.0.0-alpha.45", + "solid-js": "^1.9.12" } } diff --git a/examples/solid/column-visibility/src/App.tsx b/examples/solid/column-visibility/src/App.tsx index d972aabdd4..6406533098 100644 --- a/examples/solid/column-visibility/src/App.tsx +++ b/examples/solid/column-visibility/src/App.tsx @@ -1,75 +1,43 @@ import { - flexRender, - getCoreRowModel, - VisibilityState, - ColumnDef, - createSolidTable, + FlexRender, + columnVisibilityFeature, + createTable, + tableFeatures, } from '@tanstack/solid-table' -import { createSignal, For, Show } from 'solid-js' +import { For, Show, createSignal } from 'solid-js' +import { makeData } from './makeData' +import type { ColumnDef } from '@tanstack/solid-table' +import type { Person } from './makeData' -type Person = { - firstName: string - lastName: string - age: number - visits: number - status: string - progress: number -} +const _features = tableFeatures({ columnVisibilityFeature }) -const defaultData: Person[] = [ - { - firstName: 'tanner', - lastName: 'linsley', - age: 24, - visits: 100, - status: 'In Relationship', - progress: 50, - }, - { - firstName: 'tandy', - lastName: 'miller', - age: 40, - visits: 40, - status: 'Single', - progress: 80, - }, - { - firstName: 'joe', - lastName: 'dirte', - age: 45, - visits: 20, - status: 'Complicated', - progress: 10, - }, -] - -const defaultColumns: ColumnDef[] = [ +const defaultColumns: Array> = [ { header: 'Name', - footer: props => props.column.id, + footer: (props) => props.column.id, columns: [ { accessorKey: 'firstName', - cell: info => info.getValue(), - footer: props => props.column.id, + cell: (info) => info.getValue(), + footer: (props) => props.column.id, }, { - accessorFn: row => row.lastName, + accessorFn: (row) => row.lastName, id: 'lastName', - cell: info => info.getValue(), + cell: (info) => info.getValue(), header: () => Last Name, - footer: props => props.column.id, + footer: (props) => props.column.id, }, ], }, { header: 'Info', - footer: props => props.column.id, + footer: (props) => props.column.id, columns: [ { accessorKey: 'age', header: () => 'Age', - footer: props => props.column.id, + footer: (props) => props.column.id, }, { header: 'More Info', @@ -77,17 +45,17 @@ const defaultColumns: ColumnDef[] = [ { accessorKey: 'visits', header: () => Visits, - footer: props => props.column.id, + footer: (props) => props.column.id, }, { accessorKey: 'status', header: 'Status', - footer: props => props.column.id, + footer: (props) => props.column.id, }, { accessorKey: 'progress', header: 'Profile Progress', - footer: props => props.column.id, + footer: (props) => props.column.id, }, ], }, @@ -96,30 +64,28 @@ const defaultColumns: ColumnDef[] = [ ] function App() { - const [data, setData] = createSignal(defaultData) - const [columnVisibility, setColumnVisibility] = createSignal( - {} - ) - const rerender = () => setData(defaultData) + const [data, setData] = createSignal(makeData(20)) + const refreshData = () => setData(makeData(20)) + const stressTest = () => setData(makeData(1_000)) - const table = createSolidTable({ + const table = createTable({ + debugTable: true, + _features, get data() { return data() }, columns: defaultColumns, - state: { - get columnVisibility() { - return columnVisibility() - }, - }, - onColumnVisibilityChange: setColumnVisibility, - getCoreRowModel: getCoreRowModel(), }) return ( -
-
-
+
+
+ + +
+
+
+
- {column => ( -
+ {(column) => ( +
-
+
- {headerGroup => ( + {(headerGroup) => ( - {header => ( + {(header) => ( )} @@ -168,15 +131,12 @@ function App() { - {row => ( + {(row) => ( - {cell => ( + {(cell) => ( )} @@ -186,16 +146,13 @@ function App() { - {footerGroup => ( + {(footerGroup) => ( - {header => ( + {(header) => ( )} @@ -205,12 +162,8 @@ function App() {
- {flexRender( - header.column.columnDef.header, - header.getContext() - )} +
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} +
- {flexRender( - header.column.columnDef.footer, - header.getContext() - )} +
-
- -
-
{JSON.stringify(table.getState().columnVisibility, null, 2)}
+
+
{JSON.stringify(table.store.state, null, 2)}
) } diff --git a/examples/solid/column-visibility/src/index.css b/examples/solid/column-visibility/src/index.css index 43c09e0f6b..b9af6ded32 100644 --- a/examples/solid/column-visibility/src/index.css +++ b/examples/solid/column-visibility/src/index.css @@ -5,6 +5,8 @@ html { table { border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; } tbody { @@ -24,3 +26,340 @@ tfoot { tfoot th { font-weight: normal; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/column-visibility/src/index.tsx b/examples/solid/column-visibility/src/index.tsx index b5618861da..ea9c952177 100644 --- a/examples/solid/column-visibility/src/index.tsx +++ b/examples/solid/column-visibility/src/index.tsx @@ -1,6 +1,5 @@ /* @refresh reload */ import { render } from 'solid-js/web' - import './index.css' import App from './App' diff --git a/examples/solid/column-visibility/src/makeData.ts b/examples/solid/column-visibility/src/makeData.ts new file mode 100644 index 0000000000..b9fb014aba --- /dev/null +++ b/examples/solid/column-visibility/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/solid/column-visibility/tsconfig.json b/examples/solid/column-visibility/tsconfig.json index 7ab027b8d2..cb996fae69 100644 --- a/examples/solid/column-visibility/tsconfig.json +++ b/examples/solid/column-visibility/tsconfig.json @@ -5,8 +5,6 @@ "module": "ESNext", "lib": ["ES2020", "DOM", "DOM.Iterable"], "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, @@ -14,12 +12,10 @@ "noEmit": true, "jsx": "preserve", "jsxImportSource": "solid-js", - - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["src"] + "include": ["src", "vite.config.ts", "vite.config.js"] } diff --git a/examples/solid/composable-tables/.gitignore b/examples/solid/composable-tables/.gitignore new file mode 100644 index 0000000000..f06235c460 --- /dev/null +++ b/examples/solid/composable-tables/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/examples/solid/composable-tables/README.md b/examples/solid/composable-tables/README.md new file mode 100644 index 0000000000..b168d3c4b1 --- /dev/null +++ b/examples/solid/composable-tables/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run start` or `yarn start` diff --git a/examples/solid/composable-tables/index.html b/examples/solid/composable-tables/index.html new file mode 100644 index 0000000000..7d9b4f7c51 --- /dev/null +++ b/examples/solid/composable-tables/index.html @@ -0,0 +1,16 @@ + + + + + + + + Solid App + + + +
+ + + + diff --git a/examples/solid/composable-tables/package.json b/examples/solid/composable-tables/package.json new file mode 100644 index 0000000000..9cd6d7e5ee --- /dev/null +++ b/examples/solid/composable-tables/package.json @@ -0,0 +1,21 @@ +{ + "name": "tanstack-solid-table-example-composable-tables", + "description": "", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ./src" + }, + "license": "MIT", + "devDependencies": { + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" + }, + "dependencies": { + "@tanstack/solid-table": "^9.0.0-alpha.45", + "solid-js": "^1.9.12" + } +} diff --git a/examples/solid/composable-tables/src/App.tsx b/examples/solid/composable-tables/src/App.tsx new file mode 100644 index 0000000000..e70d37e75e --- /dev/null +++ b/examples/solid/composable-tables/src/App.tsx @@ -0,0 +1,453 @@ +import { For, createSignal } from 'solid-js' +import { createAppColumnHelper, createAppTable } from './hooks/table' +import { makeData, makeProductData } from './makeData' +import type { Person, Product } from './makeData' +// Import cell components directly - they use useCellContext internally + +// Create column helpers with TFeatures already bound - only need TData! +const personColumnHelper = createAppColumnHelper() +const productColumnHelper = createAppColumnHelper() + +// Users Table Component - Original implementation +function UsersTable() { + // Data state + const [data, setData] = createSignal(makeData(1_000)) + + // Refresh data callback + const refreshData = () => { + setData(() => makeData(1_000)) + } + const stressTest = () => { + setData(() => makeData(200_000)) + } + + // Define columns using the column helper + const columns = + // NOTE: You must use `createAppColumnHelper` instead of `createColumnHelper` when using pre-bound components like + personColumnHelper.columns([ + personColumnHelper.accessor('firstName', { + header: 'First Name', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + personColumnHelper.accessor('lastName', { + header: 'Last Name', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + personColumnHelper.accessor('age', { + header: 'Age', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + personColumnHelper.accessor('visits', { + header: 'Visits', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + personColumnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + personColumnHelper.accessor('progress', { + header: 'Progress', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + personColumnHelper.display({ + id: 'actions', + header: 'Actions', + cell: ({ cell }) => , + }), + ]) + + // Create the table - _features and _rowModels are already configured! + const table = createAppTable({ + columns, + get data() { + return data() + }, + debugTable: true, + // more table options + }) + + return ( + // Main selector on AppTable - selects all needed state in one place + ({ + // subscribe to specific states for re-rendering if you are optimizing for maximum performance + pagination: state.pagination, + sorting: state.sorting, + columnFilters: state.columnFilters, + })} + > + {(state) => { + const sorting = () => state().sorting + const columnFilters = () => state().columnFilters + return ( +
+ {/* Table toolbar using pre-bound component */} + + + {/* Table element */} + + + + {(headerGroup) => ( + + + {(h) => ( + + {(header) => ( + + )} + + )} + + + )} + + + + + {(row) => ( + + + {(c) => ( + + {(cell) => ( + + )} + + )} + + + )} + + + + + {(footerGroup) => ( + + + {(f) => ( + + {(footer) => { + const columnId = footer.column.id + const hasFilter = () => + columnFilters().some((cf) => cf.id === columnId) + + return ( + + ) + }} + + )} + + + )} + + +
+ {header.isPlaceholder ? null : ( + <> + + + + {/* Show sort order number when multiple columns sorted */} + {sorting().length > 1 && + sorting().findIndex( + (s) => s.id === header.column.id, + ) > -1 && ( + + {sorting().findIndex( + (s) => s.id === header.column.id, + ) + 1} + + )} + + )} +
+ {/* Cell components are pre-bound via AppCell */} + +
+ {footer.isPlaceholder ? null : ( + <> + {/* Use FooterSum for numeric columns, FooterColumnId for others */} + {columnId === 'age' || + columnId === 'visits' || + columnId === 'progress' ? ( + <> + + {hasFilter() && ( + + {' '} + (filtered) + + )} + + ) : columnId === 'actions' ? null : ( + <> + + {hasFilter() && ( + + {' '} + ✓ + + )} + + )} + + )} +
+ + {/* Pagination using pre-bound component */} + + + {/* Row count using pre-bound component */} + +
+ ) + }} +
+ ) +} + +// Products Table Component - New implementation using same hook and components +function ProductsTable() { + // Data state + const [data, setData] = createSignal(makeProductData(500)) + + // Refresh data callback + const refreshData = () => { + setData(makeProductData(500)) + } + const stressTest = () => { + setData(makeProductData(200_000)) + } + + // Define columns using the column helper - different structure than Users table + const columns = productColumnHelper.columns([ + productColumnHelper.accessor('name', { + header: 'Product Name', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + productColumnHelper.accessor('category', { + header: 'Category', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + productColumnHelper.accessor('price', { + header: 'Price', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + productColumnHelper.accessor('stock', { + header: 'In Stock', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + productColumnHelper.accessor('rating', { + header: 'Rating', + footer: (props) => props.column.id, + cell: ({ cell }) => , + }), + ]) + + // Create the table using the same createAppTable hook + const table = createAppTable({ + debugTable: true, + columns, + get data() { + return data() + }, + getRowId: (row) => row.id, + }) + + return ( + ({ + pagination: state.pagination, + sorting: state.sorting, + columnFilters: state.columnFilters, + })} + > + {(state) => { + const sorting = () => state().sorting + const columnFilters = () => state().columnFilters + return ( +
+ {/* Table toolbar using the same pre-bound component */} + + + {/* Table element */} + + + + {(headerGroup) => ( + + + {(h) => ( + + {(header) => ( + + )} + + )} + + + )} + + + + + {(row) => ( + + + {(c) => ( + + {(cell) => ( + + )} + + )} + + + )} + + + + + {(footerGroup) => ( + + + {(f) => ( + + {(footer) => { + const columnId = footer.column.id + const hasFilter = () => + columnFilters().some((cf) => cf.id === columnId) + + return ( + + ) + }} + + )} + + + )} + + +
+ {header.isPlaceholder ? null : ( + <> + + + + {sorting().length > 1 && + sorting().findIndex( + (s) => s.id === header.column.id, + ) > -1 && ( + + {sorting().findIndex( + (s) => s.id === header.column.id, + ) + 1} + + )} + + )} +
+ {/* Cell components are pre-bound via AppCell */} + +
+ {footer.isPlaceholder ? null : ( + <> + {/* Use FooterSum for numeric columns, FooterColumnId for others */} + {columnId === 'price' || + columnId === 'stock' || + columnId === 'rating' ? ( + <> + + {hasFilter() && ( + + {' '} + (filtered) + + )} + + ) : ( + <> + + {hasFilter() && ( + + {' '} + ✓ + + )} + + )} + + )} +
+ + {/* Pagination using the same pre-bound component */} + + + {/* Row count using the same pre-bound component */} + +
+ ) + }} +
+ ) +} + +function App() { + return ( +
+

Composable Tables Example

+

+ Both tables below use the same createAppTable hook and + shareable components, but with different data types and column + configurations. +

+ + {/* Original Users Table */} + + +
+ + {/* New Products Table */} + +
+ ) +} + +export default App diff --git a/examples/solid/composable-tables/src/components/cell-components.tsx b/examples/solid/composable-tables/src/components/cell-components.tsx new file mode 100644 index 0000000000..af78520679 --- /dev/null +++ b/examples/solid/composable-tables/src/components/cell-components.tsx @@ -0,0 +1,107 @@ +/** + * Cell-level components that use useCellContext + * + * These components can be used via the pre-bound cellComponents + * in AppCell children, e.g., + */ +import { useCellContext } from '../hooks/table' + +/** + * Generic text cell renderer + */ +export function TextCell() { + const cell = useCellContext() + return {cell.getValue()} +} + +/** + * Number cell with locale formatting + */ +export function NumberCell() { + const cell = useCellContext() + return {cell.getValue().toLocaleString()} +} + +/** + * Status badge cell for status column + */ +export function StatusCell() { + const cell = useCellContext<'relationship' | 'complicated' | 'single'>() + const status = cell.getValue() + return {status} +} + +/** + * Progress bar cell + */ +export function ProgressCell() { + const cell = useCellContext() + const progress = cell.getValue() + return ( +
+
+
+ ) +} + +/** + * Row actions cell - actions for the current row + */ +export function RowActionsCell() { + const cell = useCellContext() + const row = cell.row + + return ( +
+ + + +
+ ) +} + +/** + * Price cell with currency formatting + */ +export function PriceCell() { + const cell = useCellContext() + return ( + + $ + {cell.getValue().toLocaleString(undefined, { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })} + + ) +} + +/** + * Category badge cell + */ +export function CategoryCell() { + const cell = useCellContext<'electronics' | 'clothing' | 'food' | 'books'>() + const category = cell.getValue() + return {category} +} diff --git a/examples/solid/composable-tables/src/components/header-components.tsx b/examples/solid/composable-tables/src/components/header-components.tsx new file mode 100644 index 0000000000..63394cfb77 --- /dev/null +++ b/examples/solid/composable-tables/src/components/header-components.tsx @@ -0,0 +1,76 @@ +/** + * Header-level components that use useHeaderContext + * + * These components can be used via the pre-bound headerComponents + * in AppHeader children, e.g., + */ +import { Show } from 'solid-js' +import { useHeaderContext } from '../hooks/table' + +/** + * Sort indicator showing current sort direction + */ +export function SortIndicator() { + const header = useHeaderContext() + const sorted = () => header.column.getIsSorted() + + return ( + + {(sorted) => ( + {sorted() === 'asc' ? '🔼' : '🔽'} + )} + + ) +} + +/** + * Column filter input + */ +export function ColumnFilter() { + const header = useHeaderContext() + const canFilter = () => header.column.getCanFilter() + + const columnFilterValue = () => + (header.column.getFilterValue() ?? '') as string + + return ( + +
e.stopPropagation()}> + header.column.setFilterValue(e.target.value)} + placeholder={`Filter ${header.column.id}...`} + /> +
+
+ ) +} + +/** + * Footer showing the column ID + */ +export function FooterColumnId() { + const header = useHeaderContext() + return {header.column.id} +} + +/** + * Footer showing a summary/aggregation for numeric columns + */ +export function FooterSum() { + const header = useHeaderContext() + const table = header.getContext().table + const rows = table.getFilteredRowModel().rows + + // Calculate sum for numeric columns + const sum = () => + rows.reduce((acc, row) => { + const value = row.getValue(header.column.id) + return acc + (typeof value === 'number' ? value : 0) + }, 0) + + return ( + {sum() > 0 ? sum().toLocaleString() : '—'} + ) +} diff --git a/examples/solid/composable-tables/src/components/table-components.tsx b/examples/solid/composable-tables/src/components/table-components.tsx new file mode 100644 index 0000000000..cd1d799338 --- /dev/null +++ b/examples/solid/composable-tables/src/components/table-components.tsx @@ -0,0 +1,121 @@ +/** + * Table-level components that use useTableContext + * + * These components can be used via the pre-bound tableComponents + * directly on the table object, e.g., + */ +import { For, createMemo } from 'solid-js' +import { useTableContext } from '../hooks/table' + +/** + * Pagination controls for the table + */ +export function PaginationControls() { + const table = useTableContext() + + const pagination = createMemo(() => table.store.state.pagination) + + return ( + + ) +} + +/** + * Row count display + */ +export function RowCount() { + const table = useTableContext() + + return ( +
+ Showing {table.getRowModel().rows.length.toLocaleString()} of{' '} + {table.getRowCount().toLocaleString()} rows +
+ ) +} + +/** + * Table toolbar with title and actions + */ +export function TableToolbar({ + title, + onRefresh, + onStressTest, +}: { + title: string + onRefresh?: () => void + onStressTest?: () => void +}) { + const table = useTableContext() + + return ( +
+

{title}

+
+ {onRefresh && } + {onStressTest && ( + + )} + + +
+
+ ) +} diff --git a/examples/solid/composable-tables/src/hooks/table.ts b/examples/solid/composable-tables/src/hooks/table.ts new file mode 100644 index 0000000000..308b20db27 --- /dev/null +++ b/examples/solid/composable-tables/src/hooks/table.ts @@ -0,0 +1,105 @@ +/** + * Custom table hook setup using createTableHook + * + * This file creates a custom useAppTable hook with pre-bound components. + * Features, row models, and default options are defined once here and shared across all tables. + * Context hooks and a pre-bound createAppColumnHelper are also exported. + */ +import { + columnFilteringFeature, + createFilteredRowModel, + createPaginatedRowModel, + createSortedRowModel, + createTableHook, + filterFns, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/solid-table' + +// Import table-level components +import { + PaginationControls, + RowCount, + TableToolbar, +} from '../components/table-components' + +// Import cell-level components +import { + CategoryCell, + NumberCell, + PriceCell, + ProgressCell, + RowActionsCell, + StatusCell, + TextCell, +} from '../components/cell-components' + +// Import header/footer-level components (both use useHeaderContext) +import { + ColumnFilter, + FooterColumnId, + FooterSum, + SortIndicator, +} from '../components/header-components' + +/** + * Create the custom table hook with all pre-bound components. + * This exports: + * - createAppColumnHelper: Create column definitions with TFeatures already bound + * - useAppTable: Hook for creating tables with TFeatures baked in + * - useTableContext: Access table instance in tableComponents + * - useCellContext: Access cell instance in cellComponents + * - useHeaderContext: Access header instance in headerComponents + */ +export const { + createAppColumnHelper, + createAppTable, + useTableContext, + useCellContext, + useHeaderContext, +} = createTableHook({ + // Features are set once here and shared across all tables + _features: tableFeatures({ + columnFilteringFeature, + rowPaginationFeature, + rowSortingFeature, + }), + + // Row models are set once here + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + }, + + // set any default table options here too + getRowId: (row) => row.id, + + // Register table-level components (accessible via table.ComponentName) + tableComponents: { + PaginationControls, + RowCount, + TableToolbar, + }, + + // Register cell-level components (accessible via cell.ComponentName in AppCell) + cellComponents: { + TextCell, + NumberCell, + StatusCell, + ProgressCell, + RowActionsCell, + PriceCell, + CategoryCell, + }, + + // Register header/footer-level components (accessible via header.ComponentName in AppHeader/AppFooter) + headerComponents: { + SortIndicator, + ColumnFilter, + FooterColumnId, + FooterSum, + }, +}) diff --git a/examples/solid/composable-tables/src/index.css b/examples/solid/composable-tables/src/index.css new file mode 100644 index 0000000000..4d8fef0142 --- /dev/null +++ b/examples/solid/composable-tables/src/index.css @@ -0,0 +1,605 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + width: 100%; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th, +td { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 8px 12px; + text-align: left; +} + +th { + background-color: #f5f5f5; + font-weight: 600; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +.table-container { + padding: 16px; + max-width: 1200px; + margin: 0 auto; + border-collapse: collapse; + border-spacing: 0; +} + +.pagination { + display: flex; + align-items: center; + gap: 8px; + margin-top: 16px; + flex-wrap: wrap; +} + +.pagination button { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px 8px; + cursor: pointer; + background: white; +} + +.pagination button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.pagination input { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px; + width: 64px; +} + +.pagination select { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px; +} + +.sort-indicator { + margin-left: 4px; +} + +.column-filter { + margin-top: 4px; +} + +.column-filter input { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px; + width: 100%; + font-size: 12px; +} + +.sortable-header { + cursor: pointer; + user-select: none; +} + +.sortable-header:hover { + background-color: #e8e8e8; +} + +.row-actions { + display: flex; + gap: 4px; +} + +.row-actions button { + border: 1px solid #ccc; + border-radius: 4px; + padding: 2px 8px; + cursor: pointer; + background: white; + font-size: 12px; +} + +.row-actions button:hover { + background-color: #f0f0f0; +} + +.status-badge { + display: inline-block; + padding: 2px 8px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; +} + +.status-badge.relationship { + background-color: #d4edda; + color: #155724; +} + +.status-badge.complicated { + background-color: #fff3cd; + color: #856404; +} + +.status-badge.single { + background-color: #cce5ff; + color: #004085; +} + +.progress-bar { + width: 100%; + height: 8px; + background-color: #e9ecef; + border-radius: 4px; + overflow: hidden; +} + +.progress-bar-fill { + height: 100%; + background-color: #007bff; + transition: width 0.2s; +} + +.table-toolbar { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + flex-wrap: wrap; + gap: 8px; + border-collapse: collapse; + border-spacing: 0; +} + +.table-toolbar h2 { + margin: 0; + border-collapse: collapse; + border-spacing: 0; +} + +.table-toolbar-actions { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.table-toolbar button { + border: 1px solid #ccc; + border-radius: 4px; + padding: 8px 16px; + cursor: pointer; + background: white; + border-collapse: collapse; + border-spacing: 0; +} + +.table-toolbar button:hover { + background-color: #f0f0f0; + border-collapse: collapse; + border-spacing: 0; +} + +.row-count { + color: #666; + font-size: 14px; + margin-top: 8px; +} + +.app { + padding: 16px; +} + +.app h1 { + text-align: center; + margin-bottom: 8px; +} + +.description { + text-align: center; + color: #666; + margin-bottom: 32px; +} + +.description code { + background-color: #f5f5f5; + padding: 2px 6px; + border-radius: 4px; + font-size: 13px; +} + +.table-divider { + height: 48px; + border-bottom: 1px solid #e0e0e0; + margin: 32px auto; + max-width: 1200px; + border-collapse: collapse; + border-spacing: 0; +} + +.category-badge { + display: inline-block; + padding: 2px 8px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; + text-transform: capitalize; +} + +.category-badge.electronics { + background-color: #e3f2fd; + color: #1565c0; +} + +.category-badge.clothing { + background-color: #fce4ec; + color: #c2185b; +} + +.category-badge.food { + background-color: #e8f5e9; + color: #2e7d32; +} + +.category-badge.books { + background-color: #fff8e1; + color: #f57c00; +} + +.price { + font-weight: 600; + color: #2e7d32; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/composable-tables/src/index.tsx b/examples/solid/composable-tables/src/index.tsx new file mode 100644 index 0000000000..ea9c952177 --- /dev/null +++ b/examples/solid/composable-tables/src/index.tsx @@ -0,0 +1,6 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import './index.css' +import App from './App' + +render(() => , document.getElementById('root') as HTMLElement) diff --git a/examples/solid/composable-tables/src/makeData.ts b/examples/solid/composable-tables/src/makeData.ts new file mode 100644 index 0000000000..17dec1c6de --- /dev/null +++ b/examples/solid/composable-tables/src/makeData.ts @@ -0,0 +1,77 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +export type Product = { + id: string + name: string + category: 'electronics' | 'clothing' | 'food' | 'books' + price: number + stock: number + rating: number +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +const newProduct = (): Product => { + return { + id: faker.string.uuid(), + name: faker.commerce.productName(), + category: faker.helpers.shuffle([ + 'electronics', + 'clothing', + 'food', + 'books', + ])[0], + price: parseFloat(faker.commerce.price({ min: 5, max: 500 })), + stock: faker.number.int({ min: 0, max: 200 }), + rating: faker.number.int({ min: 0, max: 100 }), + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} + +export function makeProductData(count: number): Array { + return range(count).map(() => newProduct()) +} diff --git a/examples/solid/composable-tables/tsconfig.json b/examples/solid/composable-tables/tsconfig.json new file mode 100644 index 0000000000..cb996fae69 --- /dev/null +++ b/examples/solid/composable-tables/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts", "vite.config.js"] +} diff --git a/examples/solid/composable-tables/vite.config.ts b/examples/solid/composable-tables/vite.config.ts new file mode 100644 index 0000000000..d27427972d --- /dev/null +++ b/examples/solid/composable-tables/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite' +import solidPlugin from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solidPlugin()], + build: { + target: 'esnext', + polyfillDynamicImport: false, + }, +}) diff --git a/examples/solid/expanding/index.html b/examples/solid/expanding/index.html new file mode 100644 index 0000000000..4d3220bdd2 --- /dev/null +++ b/examples/solid/expanding/index.html @@ -0,0 +1,13 @@ + + + + + + Solid App + + + +
+ + + diff --git a/examples/solid/expanding/package.json b/examples/solid/expanding/package.json new file mode 100644 index 0000000000..570037ae30 --- /dev/null +++ b/examples/solid/expanding/package.json @@ -0,0 +1,22 @@ +{ + "name": "tanstack-solid-table-example-expanding", + "description": "", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ./src" + }, + "license": "MIT", + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" + }, + "dependencies": { + "@tanstack/solid-table": "^9.0.0-alpha.45", + "solid-js": "^1.9.12" + } +} diff --git a/examples/solid/expanding/src/App.tsx b/examples/solid/expanding/src/App.tsx new file mode 100644 index 0000000000..1dc5281707 --- /dev/null +++ b/examples/solid/expanding/src/App.tsx @@ -0,0 +1,311 @@ +import { + columnFilteringFeature, + createColumnHelper, + createExpandedRowModel, + createFilteredRowModel, + createPaginatedRowModel, + createSortedRowModel, + createTable, + filterFns, + rowExpandingFeature, + rowPaginationFeature, + rowSelectionFeature, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/solid-table' +import { For, Show, createEffect, createSignal } from 'solid-js' +import { makeData } from './makeData' +import type { Column, Table } from '@tanstack/solid-table' +import type { Person } from './makeData' + +const _features = tableFeatures({ + columnFilteringFeature, + rowExpandingFeature, + rowPaginationFeature, + rowSortingFeature, + rowSelectionFeature, +}) + +const columnHelper = createColumnHelper() + +function App() { + const [data, setData] = createSignal(makeData(100, 5, 3)) + const refreshData = () => setData(makeData(100, 5, 3)) + const stressTest = () => setData(makeData(10_000, 5, 3)) + + const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + header: ({ table }) => ( + <> + {' '} + {' '} + First Name + + ), + cell: ({ row, getValue }) => ( +
+
+ {' '} + {row.getCanExpand() ? ( + + ) : ( + '🔵' + )}{' '} + {getValue()} +
+
+ ), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }), + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + filterFn: 'between', + }), + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), + ]) + + const table = createTable({ + _features, + _rowModels: { + expandedRowModel: createExpandedRowModel(), + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, + columns, + get data() { + return data() + }, + getSubRows: (row) => row.subRows, + debugTable: true, + }) + + return ( +
+
+ + +
+
+ + + + {(headerGroup) => ( + + + {(header) => ( + + )} + + + )} + + + + + {(row) => ( + + + {(cell) => ( + + )} + + + )} + + +
+ +
+ + +
+ +
+
+
+
+
+ +
+
+
+ + + + + +
Page
+ + {(table.store.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = e.currentTarget.value + ? Number(e.currentTarget.value) - 1 + : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
+
{table.getRowModel().rows.length.toLocaleString()} Rows
+
{JSON.stringify(table.store.state, null, 2)}
+
+ ) +} + +function Filter({ + column, + table, +}: { + column: Column + table: Table +}) { + const firstValue = table + .getPreFilteredRowModel() + .flatRows[0]?.getValue(column.id) + + const columnFilterValue = () => column.getFilterValue() + + return typeof firstValue === 'number' ? ( +
+ + column.setFilterValue((old: [number, number] | undefined) => [ + e.currentTarget.value, + old?.[1], + ]) + } + placeholder="Min" + class="filter-input" + /> + + column.setFilterValue((old: [number, number] | undefined) => [ + old?.[0], + e.currentTarget.value, + ]) + } + placeholder="Max" + class="filter-input" + /> +
+ ) : ( + column.setFilterValue(e.currentTarget.value)} + placeholder="Search..." + class="filter-select" + /> + ) +} + +function IndeterminateCheckbox(props: { + indeterminate?: boolean + checked?: boolean + className?: string + onChange?: (event: Event) => void +}) { + let ref: HTMLInputElement | undefined + + createEffect(() => { + if (typeof props.indeterminate === 'boolean' && ref) { + ref.indeterminate = !props.checked && props.indeterminate + } + }) + + return ( + + ) +} + +export default App diff --git a/examples/solid/expanding/src/index.css b/examples/solid/expanding/src/index.css new file mode 100644 index 0000000000..40a5eb73a4 --- /dev/null +++ b/examples/solid/expanding/src/index.css @@ -0,0 +1,360 @@ +html { + font-family: sans-serif; + font-size: 14px; +} +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} +tbody { + border-bottom: 1px solid lightgray; +} +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} +tfoot { + color: gray; +} +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/expanding/src/index.tsx b/examples/solid/expanding/src/index.tsx new file mode 100644 index 0000000000..ea9c952177 --- /dev/null +++ b/examples/solid/expanding/src/index.tsx @@ -0,0 +1,6 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import './index.css' +import App from './App' + +render(() => , document.getElementById('root') as HTMLElement) diff --git a/examples/solid/expanding/src/makeData.ts b/examples/solid/expanding/src/makeData.ts new file mode 100644 index 0000000000..d63c724b74 --- /dev/null +++ b/examples/solid/expanding/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/solid/expanding/tsconfig.json b/examples/solid/expanding/tsconfig.json new file mode 100644 index 0000000000..cb996fae69 --- /dev/null +++ b/examples/solid/expanding/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts", "vite.config.js"] +} diff --git a/examples/solid/expanding/vite.config.ts b/examples/solid/expanding/vite.config.ts new file mode 100644 index 0000000000..f620d3461f --- /dev/null +++ b/examples/solid/expanding/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import solidPlugin from 'vite-plugin-solid' +export default defineConfig({ + plugins: [solidPlugin()], + build: { target: 'esnext' }, +}) diff --git a/examples/solid/filters-faceted/.gitignore b/examples/solid/filters-faceted/.gitignore new file mode 100644 index 0000000000..76add878f8 --- /dev/null +++ b/examples/solid/filters-faceted/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist \ No newline at end of file diff --git a/examples/solid/filters-faceted/README.md b/examples/solid/filters-faceted/README.md new file mode 100644 index 0000000000..b168d3c4b1 --- /dev/null +++ b/examples/solid/filters-faceted/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run start` or `yarn start` diff --git a/examples/solid/filters-faceted/index.html b/examples/solid/filters-faceted/index.html new file mode 100644 index 0000000000..7d9b4f7c51 --- /dev/null +++ b/examples/solid/filters-faceted/index.html @@ -0,0 +1,16 @@ + + + + + + + + Solid App + + + +
+ + + + diff --git a/examples/solid/filters-faceted/package.json b/examples/solid/filters-faceted/package.json new file mode 100644 index 0000000000..c92528e097 --- /dev/null +++ b/examples/solid/filters-faceted/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-solid-table-example-filters-faceted", + "description": "", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ./src" + }, + "license": "MIT", + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" + }, + "dependencies": { + "@tanstack/solid-pacer": "^0.21.0", + "@tanstack/solid-table": "^9.0.0-alpha.45", + "solid-js": "^1.9.12" + } +} diff --git a/examples/solid/filters-faceted/src/App.tsx b/examples/solid/filters-faceted/src/App.tsx new file mode 100644 index 0000000000..bd42b0d89b --- /dev/null +++ b/examples/solid/filters-faceted/src/App.tsx @@ -0,0 +1,243 @@ +import { + FlexRender, + columnFacetingFeature, + columnFilteringFeature, + createFacetedMinMaxValues, + createFacetedRowModel, + createFacetedUniqueValues, + createFilteredRowModel, + createPaginatedRowModel, + createTable, + filterFns, + globalFilteringFeature, + rowPaginationFeature, + tableFeatures, +} from '@tanstack/solid-table' +import { createDebouncer } from '@tanstack/solid-pacer/debouncer' +import { For, createSignal } from 'solid-js' +import { makeData } from './makeData' +import ColumnFilter from './ColumnFilter' +import type { Person } from './makeData' +import type { ColumnDef } from '@tanstack/solid-table' + +export const _features = tableFeatures({ + columnFilteringFeature, + globalFilteringFeature, + columnFacetingFeature, + rowPaginationFeature, +}) + +const columns: Array> = [ + { + header: 'Name', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'firstName', + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }, + ], + }, + { + header: 'Info', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'age', + header: () => 'Age', + footer: (props) => props.column.id, + }, + { + header: 'More Info', + columns: [ + { + accessorKey: 'visits', + header: () => Visits, + footer: (props) => props.column.id, + }, + { + accessorKey: 'status', + header: 'Status', + footer: (props) => props.column.id, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + footer: (props) => props.column.id, + }, + ], + }, + ], + }, +] + +function App() { + const [data, setData] = createSignal(makeData(1_000)) + const refreshData = () => setData(makeData(1_000)) + const stressTest = () => setData(makeData(200_000)) + + const table = createTable({ + _features, + _rowModels: { + facetedRowModel: createFacetedRowModel(), + facetedMinMaxValues: createFacetedMinMaxValues(), + facetedUniqueValues: createFacetedUniqueValues(), + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + }, + get data() { + return data() + }, + columns, + globalFilterFn: 'includesString', + debugTable: true, + debugHeaders: true, + debugColumns: false, + }) + + const globalFilterDebouncer = createDebouncer( + (value: string) => table.setGlobalFilter(value), + { wait: 500 }, + ) + + return ( +
+
+ + +
+ + globalFilterDebouncer.maybeExecute(e.currentTarget.value) + } + placeholder="Search all columns..." + /> +
+ + + + {(headerGroup) => ( + + + {(header) => ( + + )} + + + )} + + + + + {(row) => ( + + + {(cell) => ( + + )} + + + )} + + +
+ {header.isPlaceholder ? null : ( + <> + + {header.column.getCanFilter() ? ( +
+ +
+ ) : null} + + )} +
+ +
+
+
+ + + + + +
Page
+ + {(table.store.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = e.currentTarget.value + ? Number(e.currentTarget.value) - 1 + : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
+
+ Showing {table.getRowModel().rows.length.toLocaleString()} of{' '} + {table.getPrePaginatedRowModel().rows.length.toLocaleString()} Rows +
+
{JSON.stringify(table.store.state, null, 2)}
+
+ ) +} + +export default App diff --git a/examples/solid/filters-faceted/src/ColumnFilter.tsx b/examples/solid/filters-faceted/src/ColumnFilter.tsx new file mode 100644 index 0000000000..11f03f0268 --- /dev/null +++ b/examples/solid/filters-faceted/src/ColumnFilter.tsx @@ -0,0 +1,98 @@ +import { createDebouncer } from '@tanstack/solid-pacer/debouncer' +import { For, Show, createMemo } from 'solid-js' +import type { Person } from './makeData' +import type { _features } from './App' +import type { Column, Table } from '@tanstack/solid-table' + +function ColumnFilter(props: { + column: Column + table: Table +}) { + const firstValue = props.table + .getPreFilteredRowModel() + .flatRows[0]?.getValue(props.column.id) + + const columnFilterValue = () => props.column.getFilterValue() + const columnFilterDebouncer = createDebouncer( + (value: unknown) => props.column.setFilterValue(value), + { wait: 500 }, + ) + + const sortedUniqueValues = createMemo(() => + typeof firstValue === 'number' + ? [] + : Array.from(props.column.getFacetedUniqueValues().keys()).sort(), + ) + + return ( + + + + {(value: string) => + + + columnFilterDebouncer.maybeExecute(e.currentTarget.value) + } + placeholder={`Search... (${props.column.getFacetedUniqueValues().size})`} + class="filter-select" + list={`${props.column.id}list`} + /> +
+ } + > +
+
+ + columnFilterDebouncer.maybeExecute((old: [number, number]) => [ + e.currentTarget.value, + old[1], + ]) + } + placeholder={`Min ${ + props.column.getFacetedMinMaxValues()?.[0] + ? `(${props.column.getFacetedMinMaxValues()?.[0]})` + : '' + }`} + class="filter-input" + /> + + columnFilterDebouncer.maybeExecute((old: [number, number]) => [ + old[0], + e.currentTarget.value, + ]) + } + placeholder={`Max ${ + props.column.getFacetedMinMaxValues()?.[1] + ? `(${props.column.getFacetedMinMaxValues()?.[1]})` + : '' + }`} + class="filter-input" + /> +
+
+ + ) +} + +export default ColumnFilter diff --git a/examples/solid/filters-faceted/src/index.css b/examples/solid/filters-faceted/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/solid/filters-faceted/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/filters-faceted/src/index.tsx b/examples/solid/filters-faceted/src/index.tsx new file mode 100644 index 0000000000..ea9c952177 --- /dev/null +++ b/examples/solid/filters-faceted/src/index.tsx @@ -0,0 +1,6 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import './index.css' +import App from './App' + +render(() => , document.getElementById('root') as HTMLElement) diff --git a/examples/solid/filters-faceted/src/makeData.ts b/examples/solid/filters-faceted/src/makeData.ts new file mode 100644 index 0000000000..b9fb014aba --- /dev/null +++ b/examples/solid/filters-faceted/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/solid/filters-faceted/tsconfig.json b/examples/solid/filters-faceted/tsconfig.json new file mode 100644 index 0000000000..cb996fae69 --- /dev/null +++ b/examples/solid/filters-faceted/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts", "vite.config.js"] +} diff --git a/examples/solid/filters-faceted/vite.config.ts b/examples/solid/filters-faceted/vite.config.ts new file mode 100644 index 0000000000..d27427972d --- /dev/null +++ b/examples/solid/filters-faceted/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite' +import solidPlugin from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solidPlugin()], + build: { + target: 'esnext', + polyfillDynamicImport: false, + }, +}) diff --git a/examples/solid/filters-fuzzy/index.html b/examples/solid/filters-fuzzy/index.html new file mode 100644 index 0000000000..4d3220bdd2 --- /dev/null +++ b/examples/solid/filters-fuzzy/index.html @@ -0,0 +1,13 @@ + + + + + + Solid App + + + +
+ + + diff --git a/examples/solid/filters-fuzzy/package.json b/examples/solid/filters-fuzzy/package.json new file mode 100644 index 0000000000..cc5e380ade --- /dev/null +++ b/examples/solid/filters-fuzzy/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-solid-table-example-filters-fuzzy", + "description": "", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ./src" + }, + "license": "MIT", + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" + }, + "dependencies": { + "@tanstack/match-sorter-utils": "^9.0.0-alpha.4", + "@tanstack/solid-pacer": "^0.21.0", + "@tanstack/solid-table": "^9.0.0-alpha.45", + "solid-js": "^1.9.12" + } +} diff --git a/examples/solid/filters-fuzzy/src/App.tsx b/examples/solid/filters-fuzzy/src/App.tsx new file mode 100644 index 0000000000..e70a2ea3ab --- /dev/null +++ b/examples/solid/filters-fuzzy/src/App.tsx @@ -0,0 +1,307 @@ +import { + columnFilteringFeature, + createColumnHelper, + createFilteredRowModel, + createPaginatedRowModel, + createSortedRowModel, + createTable, + filterFns, + globalFilteringFeature, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/solid-table' +import { createDebouncer } from '@tanstack/solid-pacer/debouncer' +import { compareItems, rankItem } from '@tanstack/match-sorter-utils' +import { For, createEffect, createSignal } from 'solid-js' +import { makeData } from './makeData' +import type { Column, FilterFn, SortFn } from '@tanstack/solid-table' +import type { RankingInfo } from '@tanstack/match-sorter-utils' +import type { Person } from './makeData' + +const _features = tableFeatures({ + columnFilteringFeature, + globalFilteringFeature, + rowSortingFeature, + rowPaginationFeature, +}) + +const columnHelper = createColumnHelper() + +const fuzzyFilter: FilterFn = ( + row, + columnId, + value, + addMeta, +) => { + const itemRank = rankItem(row.getValue(columnId), value) + addMeta?.({ itemRank }) + return itemRank.passed +} + +const fuzzySort: SortFn = (rowA, rowB, columnId) => { + let dir = 0 + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (rowA.columnFiltersMeta[columnId]) { + dir = compareItems( + rowA.columnFiltersMeta[columnId].itemRank!, + rowB.columnFiltersMeta[columnId].itemRank!, + ) + } + return dir === 0 ? sortFns.alphanumeric(rowA, rowB, columnId) : dir +} + +declare module '@tanstack/solid-table' { + interface FilterFns { + fuzzy: FilterFn + } + interface FilterMeta { + itemRank?: RankingInfo + } +} + +const columns = columnHelper.columns([ + columnHelper.accessor('id', { + filterFn: 'equalsString', + }), + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + filterFn: 'includesStringSensitive', + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + filterFn: 'includesString', + }), + columnHelper.accessor((row) => `${row.firstName} ${row.lastName}`, { + id: 'fullName', + header: 'Full Name', + cell: (info) => info.getValue(), + filterFn: 'fuzzy', + sortFn: fuzzySort, + }), +]) + +function App() { + const [data, setData] = createSignal>(makeData(5_000)) + const refreshData = () => setData(makeData(5_000)) + const stressTest = () => setData(makeData(200_000)) + + const table = createTable({ + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel({ + ...filterFns, + fuzzy: fuzzyFilter, + }), + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, + columns, + get data() { + return data() + }, + globalFilterFn: 'fuzzy', + debugTable: true, + debugHeaders: true, + debugColumns: false, + }) + + createEffect(() => { + if (table.store.state.columnFilters[0]?.id === 'fullName') { + if (table.store.state.sorting[0]?.id !== 'fullName') { + table.setSorting([{ id: 'fullName', desc: false }]) + } + } + }) + + return ( +
+
+ + +
+
+ table.setGlobalFilter(String(value))} + class="summary-panel" + placeholder="Search all columns..." + /> +
+
+ + + + {(headerGroup) => ( + + + {(header) => ( + + )} + + + )} + + + + + {(row) => ( + + + {(cell) => ( + + )} + + + )} + + +
+ {header.isPlaceholder ? null : ( + <> +
+ + {( + { + asc: ' 🔼', + desc: ' 🔽', + } as Record + )[header.column.getIsSorted() as string] ?? null} +
+ {header.column.getCanFilter() ? ( +
+ +
+ ) : null} + + )} +
+ +
+
+
+ + + + + +
Page
+ + {(table.store.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = e.currentTarget.value + ? Number(e.currentTarget.value) - 1 + : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
+
+ {table.getPrePaginatedRowModel().rows.length.toLocaleString()} Rows +
+
{JSON.stringify(table.store.state, null, 2)}
+
+ ) +} + +function Filter({ column }: { column: Column }) { + const columnFilterValue = () => column.getFilterValue() + + return ( + column.setFilterValue(value)} + placeholder="Search..." + class="filter-select" + /> + ) +} + +function DebouncedInput(props: { + value: string | number + onChange: (value: string | number) => void + debounce?: number + type?: string + placeholder?: string + class?: string +}) { + const [value, setValue] = createSignal(props.value) + + createEffect(() => { + setValue(props.value) + }) + + const onChangeDebouncer = createDebouncer( + (nextValue: string | number) => props.onChange(nextValue), + { wait: () => props.debounce ?? 500 }, + ) + + createEffect(() => { + onChangeDebouncer.maybeExecute(value()) + }) + + return ( + setValue(e.currentTarget.value)} + placeholder={props.placeholder} + class={props.class} + /> + ) +} + +export default App diff --git a/examples/solid/filters-fuzzy/src/index.css b/examples/solid/filters-fuzzy/src/index.css new file mode 100644 index 0000000000..40a5eb73a4 --- /dev/null +++ b/examples/solid/filters-fuzzy/src/index.css @@ -0,0 +1,360 @@ +html { + font-family: sans-serif; + font-size: 14px; +} +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} +tbody { + border-bottom: 1px solid lightgray; +} +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} +tfoot { + color: gray; +} +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/filters-fuzzy/src/index.tsx b/examples/solid/filters-fuzzy/src/index.tsx new file mode 100644 index 0000000000..ea9c952177 --- /dev/null +++ b/examples/solid/filters-fuzzy/src/index.tsx @@ -0,0 +1,6 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import './index.css' +import App from './App' + +render(() => , document.getElementById('root') as HTMLElement) diff --git a/examples/solid/filters-fuzzy/src/makeData.ts b/examples/solid/filters-fuzzy/src/makeData.ts new file mode 100644 index 0000000000..38c1db1f15 --- /dev/null +++ b/examples/solid/filters-fuzzy/src/makeData.ts @@ -0,0 +1,45 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + id: number + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (num: number): Person => ({ + id: num, + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (index): Person => ({ + ...newPerson(index), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/solid/filters-fuzzy/tsconfig.json b/examples/solid/filters-fuzzy/tsconfig.json new file mode 100644 index 0000000000..cb996fae69 --- /dev/null +++ b/examples/solid/filters-fuzzy/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts", "vite.config.js"] +} diff --git a/examples/solid/filters-fuzzy/vite.config.ts b/examples/solid/filters-fuzzy/vite.config.ts new file mode 100644 index 0000000000..f620d3461f --- /dev/null +++ b/examples/solid/filters-fuzzy/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import solidPlugin from 'vite-plugin-solid' +export default defineConfig({ + plugins: [solidPlugin()], + build: { target: 'esnext' }, +}) diff --git a/examples/solid/filters/package.json b/examples/solid/filters/package.json index fe71bc1d18..1fba41aa69 100644 --- a/examples/solid/filters/package.json +++ b/examples/solid/filters/package.json @@ -1,23 +1,23 @@ { - "name": "tanstack-table-example-solid-filters", - "version": "0.0.0", + "name": "tanstack-solid-table-example-filters", "description": "", "scripts": { "start": "vite", "dev": "vite", "build": "vite build", - "serve": "vite preview" + "serve": "vite preview", + "lint": "eslint ./src" }, "license": "MIT", "devDependencies": { - "@faker-js/faker": "^8.4.1", - "typescript": "5.4.5", - "vite": "^5.3.2", - "vite-plugin-solid": "^2.10.2" + "@faker-js/faker": "^10.4.0", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" }, "dependencies": { - "@solid-primitives/scheduled": "^1.4.3", - "@tanstack/solid-table": "^8.20.5", - "solid-js": "^1.8.18" + "@tanstack/solid-pacer": "^0.21.0", + "@tanstack/solid-table": "^9.0.0-alpha.45", + "solid-js": "^1.9.12" } } diff --git a/examples/solid/filters/src/App.tsx b/examples/solid/filters/src/App.tsx index c9c8989657..f9869a1770 100644 --- a/examples/solid/filters/src/App.tsx +++ b/examples/solid/filters/src/App.tsx @@ -1,46 +1,60 @@ import { - flexRender, - getCoreRowModel, - getFilteredRowModel, - getFacetedRowModel, - getFacetedUniqueValues, - getFacetedMinMaxValues, - ColumnDef, - ColumnFiltersState, - createSolidTable, + FlexRender, + columnFacetingFeature, + columnFilteringFeature, + createFacetedMinMaxValues, + createFacetedRowModel, + createFacetedUniqueValues, + createFilteredRowModel, + createPaginatedRowModel, + createTable, + filterFns, + globalFilteringFeature, + rowPaginationFeature, + tableFeatures, } from '@tanstack/solid-table' -import { debounce } from '@solid-primitives/scheduled' -import { makeData, Person } from './makeData' +import { createDebouncer } from '@tanstack/solid-pacer/debouncer' +import { For, createSignal } from 'solid-js' +import { makeData } from './makeData' import ColumnFilter from './ColumnFilter' -import { createSignal, For } from 'solid-js' +import type { Person } from './makeData' +import type { ColumnDef } from '@tanstack/solid-table' -const columns: ColumnDef[] = [ +export const _features = tableFeatures({ + columnFilteringFeature, + globalFilteringFeature, + columnFacetingFeature, + rowPaginationFeature, +}) + +const columns: Array> = [ { header: 'Name', - footer: props => props.column.id, + footer: (props) => props.column.id, columns: [ { accessorKey: 'firstName', - cell: info => info.getValue(), - footer: props => props.column.id, + cell: (info) => info.getValue(), + footer: (props) => props.column.id, }, { - accessorFn: row => row.lastName, + accessorFn: (row) => row.lastName, id: 'lastName', - cell: info => info.getValue(), + cell: (info) => info.getValue(), header: () => Last Name, - footer: props => props.column.id, + footer: (props) => props.column.id, }, ], }, { header: 'Info', - footer: props => props.column.id, + footer: (props) => props.column.id, columns: [ { accessorKey: 'age', header: () => 'Age', - footer: props => props.column.id, + footer: (props) => props.column.id, + filterFn: 'inNumberRange', }, { header: 'More Info', @@ -48,17 +62,19 @@ const columns: ColumnDef[] = [ { accessorKey: 'visits', header: () => Visits, - footer: props => props.column.id, + footer: (props) => props.column.id, + filterFn: 'inNumberRange', }, { accessorKey: 'status', header: 'Status', - footer: props => props.column.id, + footer: (props) => props.column.id, }, { accessorKey: 'progress', header: 'Profile Progress', - footer: props => props.column.id, + footer: (props) => props.column.id, + filterFn: 'inNumberRange', }, ], }, @@ -67,64 +83,60 @@ const columns: ColumnDef[] = [ ] function App() { - const [data, setData] = createSignal(makeData(50000)) - const [columnFilters, setColumnFilters] = createSignal([]) - const [globalFilter, setGlobalFilter] = createSignal('') - const debounceSetGlobalFilter = debounce( - (value: string) => setGlobalFilter(value), - 500 - ) - const refreshData = () => setData(makeData(50000)) + const [data, setData] = createSignal(makeData(1_000)) + const refreshData = () => setData(makeData(1_000)) + const stressTest = () => setData(makeData(200_000)) - const table = createSolidTable({ + const table = createTable({ + _features, + _rowModels: { + facetedRowModel: createFacetedRowModel(), + facetedMinMaxValues: createFacetedMinMaxValues(), + facetedUniqueValues: createFacetedUniqueValues(), + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + }, get data() { return data() }, columns, - state: { - get columnFilters() { - return columnFilters() - }, - get globalFilter() { - return globalFilter() - }, - }, - onGlobalFilterChange: setGlobalFilter, globalFilterFn: 'includesString', - onColumnFiltersChange: setColumnFilters, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getFacetedRowModel: getFacetedRowModel(), - getFacetedUniqueValues: getFacetedUniqueValues(), - getFacetedMinMaxValues: getFacetedMinMaxValues(), debugTable: true, debugHeaders: true, debugColumns: false, }) + const globalFilterDebouncer = createDebouncer( + (value: string) => table.setGlobalFilter(value), + { wait: 500 }, + ) + return ( -
+
+
+ + +
debounceSetGlobalFilter(e.currentTarget.value)} + class="summary-panel" + value={table.store.state.globalFilter ?? ''} + onInput={(e) => + globalFilterDebouncer.maybeExecute(e.currentTarget.value) + } placeholder="Search all columns..." /> -
+
- {headerGroup => ( + {(headerGroup) => ( - {header => ( + {(header) => ( - - {row => ( + + {(row) => ( - - {cell => ( + + {(cell) => ( )} @@ -161,11 +170,75 @@ function App() {
{header.isPlaceholder ? null : ( <> - {flexRender( - header.column.columnDef.header, - header.getContext() - )} + {header.column.getCanFilter() ? (
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} +
-
{table.getRowModel().rows.length} Rows
+
+
+ + + + + +
Page
+ + {(table.store.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = e.currentTarget.value + ? Number(e.currentTarget.value) - 1 + : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
- + Showing {table.getRowModel().rows.length.toLocaleString()} of{' '} + {table.getPrePaginatedRowModel().rows.length.toLocaleString()} Rows
-
{JSON.stringify(table.getState(), null, 2)}
+
{JSON.stringify(table.store.state, null, 2)}
) } diff --git a/examples/solid/filters/src/ColumnFilter.tsx b/examples/solid/filters/src/ColumnFilter.tsx index 92891e148e..65021d3b88 100644 --- a/examples/solid/filters/src/ColumnFilter.tsx +++ b/examples/solid/filters/src/ColumnFilter.tsx @@ -1,21 +1,21 @@ -import { Column, Table } from '@tanstack/solid-table' -import { debounce } from '@solid-primitives/scheduled' -import { createMemo, For, Show } from 'solid-js' +import { createDebouncer } from '@tanstack/solid-pacer/debouncer' +import { Show } from 'solid-js' +import type { Person } from './makeData' +import type { _features } from './App' +import type { Column, Table } from '@tanstack/solid-table' function ColumnFilter(props: { - column: Column - table: Table + column: Column + table: Table }) { const firstValue = props.table .getPreFilteredRowModel() .flatRows[0]?.getValue(props.column.id) const columnFilterValue = () => props.column.getFilterValue() - - const sortedUniqueValues = createMemo(() => - typeof firstValue === 'number' - ? [] - : Array.from(props.column.getFacetedUniqueValues().keys()).sort() + const columnFilterDebouncer = createDebouncer( + (value: unknown) => props.column.setFilterValue(value), + { wait: 500 }, ) return ( @@ -23,66 +23,56 @@ function ColumnFilter(props: { when={typeof firstValue === 'number'} fallback={
- - - {(value: string) => - props.column.setFilterValue(e.target.value), - 500 - )} - placeholder={`Search... (${props.column.getFacetedUniqueValues().size})`} - class="w-36 border shadow rounded" + onInput={(e) => + columnFilterDebouncer.maybeExecute(e.currentTarget.value) + } + placeholder={`Search...`} + class="filter-select" list={`${props.column.id}list`} />
} >
-
+
- props.column.setFilterValue((old: [number, number]) => [ - e.target.value, - old?.[1], - ]), - 500 - )} - placeholder={`Min ${ - props.column.getFacetedMinMaxValues()?.[0] - ? `(${props.column.getFacetedMinMaxValues()?.[0]})` - : '' - }`} - class="w-24 border shadow rounded" + min={0} + max={100} + value={ + (columnFilterValue() as [number, number] | undefined)?.[0] ?? '' + } + onInput={(e) => + columnFilterDebouncer.maybeExecute( + (old: [number, number] | undefined) => [ + e.currentTarget.value, + old?.[1] ?? '', + ], + ) + } + placeholder={`Min`} + class="filter-input" /> - props.column.setFilterValue((old: [number, number]) => [ - old?.[0], - e.target.value, - ]), - 500 - )} - placeholder={`Max ${ - props.column.getFacetedMinMaxValues()?.[1] - ? `(${props.column.getFacetedMinMaxValues()?.[1]})` - : '' - }`} - class="w-24 border shadow rounded" + min={0} + max={100} + value={ + (columnFilterValue() as [number, number] | undefined)?.[1] ?? '' + } + onInput={(e) => + columnFilterDebouncer.maybeExecute( + (old: [number, number] | undefined) => [ + old?.[0] ?? '', + e.currentTarget.value, + ], + ) + } + placeholder={`Max`} + class="filter-input" />
diff --git a/examples/solid/filters/src/index.css b/examples/solid/filters/src/index.css index 43c09e0f6b..b9af6ded32 100644 --- a/examples/solid/filters/src/index.css +++ b/examples/solid/filters/src/index.css @@ -5,6 +5,8 @@ html { table { border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; } tbody { @@ -24,3 +26,340 @@ tfoot { tfoot th { font-weight: normal; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/filters/src/index.tsx b/examples/solid/filters/src/index.tsx index b5618861da..ea9c952177 100644 --- a/examples/solid/filters/src/index.tsx +++ b/examples/solid/filters/src/index.tsx @@ -1,6 +1,5 @@ /* @refresh reload */ import { render } from 'solid-js/web' - import './index.css' import App from './App' diff --git a/examples/solid/filters/src/makeData.ts b/examples/solid/filters/src/makeData.ts index 331dd1eb19..b9fb014aba 100644 --- a/examples/solid/filters/src/makeData.ts +++ b/examples/solid/filters/src/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,13 +29,13 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] return range(len).map((d): Person => { return { ...newPerson(), diff --git a/examples/solid/filters/tsconfig.json b/examples/solid/filters/tsconfig.json index 7ab027b8d2..cb996fae69 100644 --- a/examples/solid/filters/tsconfig.json +++ b/examples/solid/filters/tsconfig.json @@ -5,8 +5,6 @@ "module": "ESNext", "lib": ["ES2020", "DOM", "DOM.Iterable"], "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, @@ -14,12 +12,10 @@ "noEmit": true, "jsx": "preserve", "jsxImportSource": "solid-js", - - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["src"] + "include": ["src", "vite.config.ts", "vite.config.js"] } diff --git a/examples/solid/grouping/index.html b/examples/solid/grouping/index.html new file mode 100644 index 0000000000..4d3220bdd2 --- /dev/null +++ b/examples/solid/grouping/index.html @@ -0,0 +1,13 @@ + + + + + + Solid App + + + +
+ + + diff --git a/examples/solid/grouping/package.json b/examples/solid/grouping/package.json new file mode 100644 index 0000000000..fbef0b768b --- /dev/null +++ b/examples/solid/grouping/package.json @@ -0,0 +1,22 @@ +{ + "name": "tanstack-solid-table-example-grouping", + "description": "", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ./src" + }, + "license": "MIT", + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" + }, + "dependencies": { + "@tanstack/solid-table": "^9.0.0-alpha.45", + "solid-js": "^1.9.12" + } +} diff --git a/examples/solid/grouping/src/App.tsx b/examples/solid/grouping/src/App.tsx new file mode 100644 index 0000000000..d6bf20e2e2 --- /dev/null +++ b/examples/solid/grouping/src/App.tsx @@ -0,0 +1,242 @@ +import { + aggregationFns, + columnFilteringFeature, + columnGroupingFeature, + createExpandedRowModel, + createFilteredRowModel, + createGroupedRowModel, + createPaginatedRowModel, + createSortedRowModel, + createTableHook, + filterFns, + rowExpandingFeature, + rowPaginationFeature, + rowSortingFeature, + sortFns, +} from '@tanstack/solid-table' +import { For, createSignal } from 'solid-js' +import { makeData } from './makeData' +import type { Person } from './makeData' + +const { createAppTable, createAppColumnHelper } = createTableHook({ + _features: { + columnFilteringFeature, + columnGroupingFeature, + rowExpandingFeature, + rowPaginationFeature, + rowSortingFeature, + }, + _rowModels: { + expandedRowModel: createExpandedRowModel(), + filteredRowModel: createFilteredRowModel(filterFns), + groupedRowModel: createGroupedRowModel(aggregationFns), + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, +}) + +const columnHelper = createAppColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + cell: (info) => info.getValue(), + /** + * override the value used for row grouping + * (otherwise, defaults to the value derived from accessorKey / accessorFn) + */ + getGroupingValue: (row) => `${row.firstName} ${row.lastName}`, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + header: () => Last Name, + cell: (info) => info.getValue(), + }), + columnHelper.accessor('age', { + header: () => 'Age', + aggregatedCell: ({ getValue }) => + Math.round(getValue() * 100) / 100, + aggregationFn: 'median', + }), + columnHelper.accessor('visits', { + header: () => Visits, + aggregationFn: 'sum', + aggregatedCell: ({ getValue }) => getValue().toLocaleString(), + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + cell: ({ getValue }) => Math.round(getValue() * 100) / 100 + '%', + aggregationFn: 'mean', + aggregatedCell: ({ getValue }) => + Math.round(getValue() * 100) / 100 + '%', + }), +]) + +function App() { + const [data, setData] = createSignal(makeData(10_000)) + const refreshData = () => setData(makeData(10_000)) + const stressTest = () => setData(makeData(200_000)) + + const table = createAppTable( + { + columns, + get data() { + return data() + }, + debugTable: true, + }, + (state) => state, + ) + + return ( +
+
+ + +
+
+ + + + {(headerGroup) => ( + + + {(header) => ( + + )} + + + )} + + + + + {(row) => ( + + + {(cell) => ( + + )} + + + )} + + +
+ {header.isPlaceholder ? null : ( +
+ {header.column.getCanGroup() ? ( + + ) : null}{' '} + +
+ )} +
+ {cell.getIsGrouped() ? ( + <> + + + ) : cell.getIsAggregated() ? ( + + ) : cell.getIsPlaceholder() ? null : ( + + )} +
+
+
+ + + + + +
Page
+ + {(table.store.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = e.currentTarget.value + ? Number(e.currentTarget.value) - 1 + : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
+
{table.getRowModel().rows.length.toLocaleString()} Rows
+
{JSON.stringify(table.store.state, null, 2)}
+
+ ) +} + +export default App diff --git a/examples/solid/grouping/src/index.css b/examples/solid/grouping/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/solid/grouping/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/grouping/src/index.tsx b/examples/solid/grouping/src/index.tsx new file mode 100644 index 0000000000..ea9c952177 --- /dev/null +++ b/examples/solid/grouping/src/index.tsx @@ -0,0 +1,6 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import './index.css' +import App from './App' + +render(() => , document.getElementById('root') as HTMLElement) diff --git a/examples/solid/grouping/src/makeData.ts b/examples/solid/grouping/src/makeData.ts new file mode 100644 index 0000000000..d63c724b74 --- /dev/null +++ b/examples/solid/grouping/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/solid/grouping/tsconfig.json b/examples/solid/grouping/tsconfig.json new file mode 100644 index 0000000000..cb996fae69 --- /dev/null +++ b/examples/solid/grouping/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts", "vite.config.js"] +} diff --git a/examples/solid/grouping/vite.config.ts b/examples/solid/grouping/vite.config.ts new file mode 100644 index 0000000000..bd4f9faf75 --- /dev/null +++ b/examples/solid/grouping/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import solidPlugin from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solidPlugin()], + build: { target: 'esnext' }, +}) diff --git a/examples/solid/pagination/index.html b/examples/solid/pagination/index.html new file mode 100644 index 0000000000..4d3220bdd2 --- /dev/null +++ b/examples/solid/pagination/index.html @@ -0,0 +1,13 @@ + + + + + + Solid App + + + +
+ + + diff --git a/examples/solid/pagination/package.json b/examples/solid/pagination/package.json new file mode 100644 index 0000000000..c5e128de01 --- /dev/null +++ b/examples/solid/pagination/package.json @@ -0,0 +1,22 @@ +{ + "name": "tanstack-solid-table-example-pagination", + "description": "", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ./src" + }, + "license": "MIT", + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" + }, + "dependencies": { + "@tanstack/solid-table": "^9.0.0-alpha.45", + "solid-js": "^1.9.12" + } +} diff --git a/examples/solid/pagination/src/App.tsx b/examples/solid/pagination/src/App.tsx new file mode 100644 index 0000000000..598f00a206 --- /dev/null +++ b/examples/solid/pagination/src/App.tsx @@ -0,0 +1,189 @@ +import { + createColumnHelper, + createPaginatedRowModel, + createTable, + rowPaginationFeature, + tableFeatures, +} from '@tanstack/solid-table' +import { For, createSignal } from 'solid-js' +import { makeData } from './makeData' +import type { Person } from './makeData' + +const _features = tableFeatures({ + rowPaginationFeature, +}) + +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }), + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + }), + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), +]) + +function App() { + const [data, setData] = createSignal(makeData(1_000)) + const refreshData = () => setData(makeData(1_000)) + const stressTest = () => setData(makeData(200_000)) + + return ( + <> +
+ + +
+ + + ) +} + +function MyTable(props: { + data: Array + columns: ReturnType +}) { + const table = createTable({ + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + }, + columns: props.columns, + get data() { + return props.data + }, + debugTable: true, + }) + + return ( +
+
+ + + + {(headerGroup) => ( + + + {(header) => ( + + )} + + + )} + + + + + {(row) => ( + + + {(cell) => ( + + )} + + + )} + + +
+
+ +
+
+ +
+
+
+ + + + + +
Page
+ + {(table.store.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = e.currentTarget.value + ? Number(e.currentTarget.value) - 1 + : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
+
+ Showing {table.getRowModel().rows.length.toLocaleString()} of{' '} + {table.getRowCount().toLocaleString()} Rows +
+
{JSON.stringify(table.store.state, null, 2)}
+
+ ) +} + +export default App diff --git a/examples/solid/pagination/src/index.css b/examples/solid/pagination/src/index.css new file mode 100644 index 0000000000..40a5eb73a4 --- /dev/null +++ b/examples/solid/pagination/src/index.css @@ -0,0 +1,360 @@ +html { + font-family: sans-serif; + font-size: 14px; +} +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} +tbody { + border-bottom: 1px solid lightgray; +} +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} +tfoot { + color: gray; +} +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/pagination/src/index.tsx b/examples/solid/pagination/src/index.tsx new file mode 100644 index 0000000000..ea9c952177 --- /dev/null +++ b/examples/solid/pagination/src/index.tsx @@ -0,0 +1,6 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import './index.css' +import App from './App' + +render(() => , document.getElementById('root') as HTMLElement) diff --git a/examples/solid/pagination/src/makeData.ts b/examples/solid/pagination/src/makeData.ts new file mode 100644 index 0000000000..d63c724b74 --- /dev/null +++ b/examples/solid/pagination/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/solid/pagination/tsconfig.json b/examples/solid/pagination/tsconfig.json new file mode 100644 index 0000000000..cb996fae69 --- /dev/null +++ b/examples/solid/pagination/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts", "vite.config.js"] +} diff --git a/examples/solid/pagination/vite.config.ts b/examples/solid/pagination/vite.config.ts new file mode 100644 index 0000000000..f620d3461f --- /dev/null +++ b/examples/solid/pagination/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import solidPlugin from 'vite-plugin-solid' +export default defineConfig({ + plugins: [solidPlugin()], + build: { target: 'esnext' }, +}) diff --git a/examples/solid/row-pinning/index.html b/examples/solid/row-pinning/index.html new file mode 100644 index 0000000000..4d3220bdd2 --- /dev/null +++ b/examples/solid/row-pinning/index.html @@ -0,0 +1,13 @@ + + + + + + Solid App + + + +
+ + + diff --git a/examples/solid/row-pinning/package.json b/examples/solid/row-pinning/package.json new file mode 100644 index 0000000000..0533e03133 --- /dev/null +++ b/examples/solid/row-pinning/package.json @@ -0,0 +1,22 @@ +{ + "name": "tanstack-solid-table-example-row-pinning", + "description": "", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ./src" + }, + "license": "MIT", + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" + }, + "dependencies": { + "@tanstack/solid-table": "^9.0.0-alpha.45", + "solid-js": "^1.9.12" + } +} diff --git a/examples/solid/row-pinning/src/App.tsx b/examples/solid/row-pinning/src/App.tsx new file mode 100644 index 0000000000..128a4fb3de --- /dev/null +++ b/examples/solid/row-pinning/src/App.tsx @@ -0,0 +1,405 @@ +import { + columnFilteringFeature, + createExpandedRowModel, + createFilteredRowModel, + createPaginatedRowModel, + createTable, + filterFns, + rowExpandingFeature, + rowPaginationFeature, + rowPinningFeature, + tableFeatures, +} from '@tanstack/solid-table' +import { For, Show, createMemo, createSignal } from 'solid-js' +import { makeData } from './makeData' +import type { + Column, + ExpandedState, + Row, + RowPinningState, + Table, +} from '@tanstack/solid-table' +import type { Person } from './makeData' + +const _features = tableFeatures({ + rowPinningFeature, + rowExpandingFeature, + columnFilteringFeature, + rowPaginationFeature, +}) + +function App() { + const [rowPinning, setRowPinning] = createSignal({ + top: [], + bottom: [], + }) + const [expanded, setExpanded] = createSignal({}) + + const [keepPinnedRows, setKeepPinnedRows] = createSignal(true) + const [includeLeafRows, setIncludeLeafRows] = createSignal(true) + const [includeParentRows, setIncludeParentRows] = createSignal(false) + const [copyPinnedRows, setCopyPinnedRows] = createSignal(false) + + const [data, setData] = createSignal(makeData(1_000, 2, 2)) + const refreshData = () => setData(makeData(1_000, 2, 2)) + const stressTest = () => setData(makeData(200_000, 2, 2)) + + const columns = createMemo(() => [ + { + id: 'pin', + header: () => 'Pin', + cell: ({ row }: { row: Row }) => + row.getIsPinned() ? ( + + ) : ( +
+ + +
+ ), + }, + { + accessorKey: 'firstName', + header: ({ table }: { table: Table }) => ( + <> + {' '} + First Name + + ), + cell: ({ + row, + getValue, + }: { + row: Row + getValue: () => unknown + }) => ( +
+ {row.getCanExpand() ? ( + + ) : ( + '🔵' + )}{' '} + {getValue() as string} +
+ ), + footer: (props: any) => props.column.id, + }, + { + accessorFn: (row: Person) => row.lastName, + id: 'lastName', + cell: (info: any) => info.getValue(), + header: () => Last Name, + }, + { accessorKey: 'age', header: () => 'Age', size: 50 }, + { accessorKey: 'visits', header: () => Visits, size: 50 }, + { accessorKey: 'status', header: 'Status' }, + { accessorKey: 'progress', header: 'Profile Progress', size: 80 }, + ]) + + const table = createTable( + { + debugTable: true, + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + expandedRowModel: createExpandedRowModel(), + paginatedRowModel: createPaginatedRowModel(), + }, + get columns() { + return columns() + }, + get data() { + return data() + }, + initialState: { pagination: { pageSize: 20, pageIndex: 0 } }, + get state() { + return { + expanded: expanded(), + rowPinning: rowPinning(), + } + }, + onExpandedChange: setExpanded, + onRowPinningChange: setRowPinning, + getSubRows: (row) => row.subRows, + get keepPinnedRows() { + return keepPinnedRows() + }, + debugAll: true, + }, + (state) => state, + ) + + return ( +
+
+ + +
+
+
+ + + + {(headerGroup) => ( + + + {(header) => ( + + )} + + + )} + + + + + {(row) => } + + + {(row) => ( + + + {(cell) => ( + + )} + + + )} + + + {(row) => } + + +
+ + <> + + +
+ +
+
+ +
+
+ +
+
+ +
+
+ + + + + +
Page
+ + {(table.store.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = e.currentTarget.value + ? Number(e.currentTarget.value) - 1 + : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
+
+
+
+
+
+ setKeepPinnedRows(!keepPinnedRows())} + /> + +
+
+ setIncludeLeafRows(!includeLeafRows())} + /> + +
+
+ setIncludeParentRows(!includeParentRows())} + /> + +
+
+ setCopyPinnedRows(!copyPinnedRows())} + /> + +
+
+
{JSON.stringify(rowPinning(), null, 2)}
+
+ ) +} + +function PinnedRow(props: { + row: Row + table: Table +}) { + return ( + + + {(cell) => ( + + + + )} + + + ) +} + +function Filter({ + column, + table, +}: { + column: Column + table: Table +}) { + const firstValue = table + .getPreFilteredRowModel() + .flatRows[0]?.getValue(column.id) + + return typeof firstValue === 'number' ? ( +
+ + column.setFilterValue((old: any) => [e.currentTarget.value, old?.[1]]) + } + placeholder="Min" + class="filter-input" + /> + + column.setFilterValue((old: any) => [old?.[0], e.currentTarget.value]) + } + placeholder="Max" + class="filter-input" + /> +
+ ) : ( + column.setFilterValue(e.currentTarget.value)} + placeholder="Search..." + class="filter-select" + /> + ) +} + +export default App diff --git a/examples/solid/row-pinning/src/index.css b/examples/solid/row-pinning/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/solid/row-pinning/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/row-pinning/src/index.tsx b/examples/solid/row-pinning/src/index.tsx new file mode 100644 index 0000000000..ea9c952177 --- /dev/null +++ b/examples/solid/row-pinning/src/index.tsx @@ -0,0 +1,6 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import './index.css' +import App from './App' + +render(() => , document.getElementById('root') as HTMLElement) diff --git a/examples/solid/row-pinning/src/makeData.ts b/examples/solid/row-pinning/src/makeData.ts new file mode 100644 index 0000000000..d63c724b74 --- /dev/null +++ b/examples/solid/row-pinning/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/solid/row-pinning/tsconfig.json b/examples/solid/row-pinning/tsconfig.json new file mode 100644 index 0000000000..cb996fae69 --- /dev/null +++ b/examples/solid/row-pinning/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts", "vite.config.js"] +} diff --git a/examples/solid/row-pinning/vite.config.ts b/examples/solid/row-pinning/vite.config.ts new file mode 100644 index 0000000000..bd4f9faf75 --- /dev/null +++ b/examples/solid/row-pinning/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import solidPlugin from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solidPlugin()], + build: { target: 'esnext' }, +}) diff --git a/examples/solid/row-selection/README.md b/examples/solid/row-selection/README.md new file mode 100644 index 0000000000..7c71a56415 --- /dev/null +++ b/examples/solid/row-selection/README.md @@ -0,0 +1,16 @@ +# Row Selection Example + +This example demonstrates row selection functionality with TanStack Table in SolidJS. + +## Features + +- Row selection with checkboxes +- Select all / deselect all functionality +- Page-level row selection +- Filtering and pagination with row selection +- Reactive reads from `table.store.state` keep the UI in sync with table state + +## Running the Example + +- `npm install` or `yarn` +- `npm run start` or `yarn start` diff --git a/examples/solid/row-selection/index.html b/examples/solid/row-selection/index.html new file mode 100644 index 0000000000..42fb7f56b6 --- /dev/null +++ b/examples/solid/row-selection/index.html @@ -0,0 +1,15 @@ + + + + + + + Solid Row Selection Example + + + +
+ + + + diff --git a/examples/solid/row-selection/package.json b/examples/solid/row-selection/package.json new file mode 100644 index 0000000000..9c47c17c3c --- /dev/null +++ b/examples/solid/row-selection/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-solid-table-example-row-selection", + "description": "", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ./src" + }, + "license": "MIT", + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" + }, + "dependencies": { + "@tanstack/solid-devtools": "^0.8.2", + "@tanstack/solid-table": "^9.0.0-alpha.45", + "@tanstack/solid-table-devtools": "^9.0.0-alpha.43", + "solid-js": "^1.9.12" + } +} diff --git a/examples/solid/row-selection/src/App.tsx b/examples/solid/row-selection/src/App.tsx new file mode 100644 index 0000000000..4f2bde120c --- /dev/null +++ b/examples/solid/row-selection/src/App.tsx @@ -0,0 +1,367 @@ +import { For, Show, createEffect, createSignal } from 'solid-js' +import { useTanStackTableDevtools } from '@tanstack/solid-table-devtools' +import { + FlexRender, + columnFilteringFeature, + createFilteredRowModel, + createPaginatedRowModel, + createTable, + filterFns, + globalFilteringFeature, + rowPaginationFeature, + rowSelectionFeature, + tableFeatures, +} from '@tanstack/solid-table' +import { makeData } from './makeData' +import type { + Column, + ColumnDef, + SolidTable, + Table, +} from '@tanstack/solid-table' +import type { Person } from './makeData' +import './index.css' + +export const _features = tableFeatures({ + rowPaginationFeature, + rowSelectionFeature, + columnFilteringFeature, + globalFilteringFeature, +}) + +function App() { + const [data, setData] = createSignal(makeData(1_000)) + const refreshData = () => setData(makeData(1_000)) + const stressTest = () => setData(makeData(200_000)) + const [enableRowSelection, setEnableRowSelection] = createSignal(true) + + const tableRef: { current?: SolidTable } = {} + + const columns: Array> = [ + { + id: 'select', + header: () => ( + + ), + cell: ({ row }) => { + return ( +
+ +
+ ) + }, + }, + { + header: 'Name', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'firstName', + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }, + ], + }, + { + header: 'Info', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'age', + header: () => 'Age', + footer: (props) => props.column.id, + }, + { + header: 'More Info', + columns: [ + { + accessorKey: 'visits', + header: () => Visits, + footer: (props) => props.column.id, + }, + { + accessorKey: 'status', + header: 'Status', + footer: (props) => props.column.id, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + footer: (props) => props.column.id, + }, + ], + }, + ], + }, + ] + + const table = createTable({ + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + }, + get data() { + return data() + }, + columns, + getRowId: (row) => row.id, + get enableRowSelection() { + return enableRowSelection() + }, + debugTable: true, + }) + tableRef.current = table + useTanStackTableDevtools(table, 'Row Selection Example') + + return ( +
+
+ + +
+
+ table.setGlobalFilter(e.target.value)} + class="summary-panel" + placeholder="Search all columns..." + /> +
+
+ + + + {(headerGroup) => ( + + + {(header) => ( + + )} + + + )} + + + + + {(row) => ( + + + {(cell) => ( + + )} + + + )} + + + + + + + + +
+ + <> + + +
+ +
+
+ +
+
+ +
+ + + Page Rows ({table.getRowModel().rows.length.toLocaleString()}) +
+
+
+ + + + + +
Page
+ + {(table.store.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = e.target.value ? Number(e.target.value) - 1 : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
+
+
+ {Object.keys(table.store.state.rowSelection).length.toLocaleString()} of{' '} + {table.getPreFilteredRowModel().rows.length.toLocaleString()} Total Rows + Selected +
+
+
+
+ + +
+
+ +
{JSON.stringify(table.store.state, null, 2)}
+
+
+ ) +} + +function Filter(props: { + column: Column + table: Table +}) { + const firstValue = props.table + .getPreFilteredRowModel() + .flatRows[0]?.getValue(props.column.id) + + return typeof firstValue === 'number' ? ( +
+ + props.column.setFilterValue((old: any) => [e.target.value, old?.[1]]) + } + placeholder={`Min`} + class="filter-input" + /> + + props.column.setFilterValue((old: any) => [old?.[0], e.target.value]) + } + placeholder={`Max`} + class="filter-input" + /> +
+ ) : ( + props.column.setFilterValue(e.target.value)} + placeholder={`Search...`} + class="filter-select" + /> + ) +} + +function IndeterminateCheckbox(props: { + indeterminate?: boolean + class?: string + checked?: boolean + disabled?: boolean + onChange?: (event: Event) => void +}) { + let ref: HTMLInputElement | undefined + + createEffect(() => { + if (typeof props.indeterminate === 'boolean' && ref) { + ref.indeterminate = !props.checked && props.indeterminate + } + }) + + return ( + + ) +} + +export default App diff --git a/examples/solid/row-selection/src/index.css b/examples/solid/row-selection/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/solid/row-selection/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/row-selection/src/index.tsx b/examples/solid/row-selection/src/index.tsx new file mode 100644 index 0000000000..dd3a8d2961 --- /dev/null +++ b/examples/solid/row-selection/src/index.tsx @@ -0,0 +1,16 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import { TanStackDevtools } from '@tanstack/solid-devtools' +import { tableDevtoolsPlugin } from '@tanstack/solid-table-devtools' +import './index.css' +import App from './App' + +render( + () => ( + <> + + + + ), + document.getElementById('root') as HTMLElement, +) diff --git a/examples/solid/row-selection/src/makeData.ts b/examples/solid/row-selection/src/makeData.ts new file mode 100644 index 0000000000..c34c43a03e --- /dev/null +++ b/examples/solid/row-selection/src/makeData.ts @@ -0,0 +1,50 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + id: string + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + id: faker.string.uuid(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/solid/row-selection/tsconfig.json b/examples/solid/row-selection/tsconfig.json new file mode 100644 index 0000000000..cb996fae69 --- /dev/null +++ b/examples/solid/row-selection/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts", "vite.config.js"] +} diff --git a/examples/solid/row-selection/vite.config.ts b/examples/solid/row-selection/vite.config.ts new file mode 100644 index 0000000000..d27427972d --- /dev/null +++ b/examples/solid/row-selection/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite' +import solidPlugin from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solidPlugin()], + build: { + target: 'esnext', + polyfillDynamicImport: false, + }, +}) diff --git a/examples/solid/sorting/package.json b/examples/solid/sorting/package.json index 2396f9b7c3..3643def371 100644 --- a/examples/solid/sorting/package.json +++ b/examples/solid/sorting/package.json @@ -1,22 +1,22 @@ { - "name": "tanstack-table-example-solid-sorting", - "version": "0.0.0", + "name": "tanstack-solid-table-example-sorting", "description": "", "scripts": { "start": "vite", "dev": "vite", "build": "vite build", - "serve": "vite preview" + "serve": "vite preview", + "lint": "eslint ./src" }, "license": "MIT", "devDependencies": { - "@faker-js/faker": "^8.4.1", - "typescript": "5.4.5", - "vite": "^5.3.2", - "vite-plugin-solid": "^2.10.2" + "@faker-js/faker": "^10.4.0", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" }, "dependencies": { - "@tanstack/solid-table": "^8.20.5", - "solid-js": "^1.8.18" + "@tanstack/solid-table": "^9.0.0-alpha.45", + "solid-js": "^1.9.12" } } diff --git a/examples/solid/sorting/src/App.tsx b/examples/solid/sorting/src/App.tsx index 8b73cc3ad8..d209d77f97 100644 --- a/examples/solid/sorting/src/App.tsx +++ b/examples/solid/sorting/src/App.tsx @@ -1,46 +1,50 @@ import { - flexRender, - getCoreRowModel, - getSortedRowModel, - SortingState, - ColumnDef, - createSolidTable, + FlexRender, + createSortedRowModel, + createTable, + rowSortingFeature, + sortFns, + tableFeatures, } from '@tanstack/solid-table' -import { makeData, Person } from './makeData' -import { createSignal, For, Show } from 'solid-js' +import { For, Show, createSignal } from 'solid-js' +import { makeData } from './makeData' +import type { ColumnDef } from '@tanstack/solid-table' +import type { Person } from './makeData' + +const _features = tableFeatures({ rowSortingFeature }) function App() { - const [data, setData] = createSignal(makeData(100_000)) - const [sorting, setSorting] = createSignal([]) - const refreshData = () => setData(makeData(100_000)) + const [data, setData] = createSignal(makeData(1_000)) + const refreshData = () => setData(makeData(1_000)) + const stressTest = () => setData(makeData(500_000)) - const columns: ColumnDef[] = [ + const columns: Array> = [ { header: 'Name', - footer: props => props.column.id, + footer: (props) => props.column.id, columns: [ { accessorKey: 'firstName', - cell: info => info.getValue(), - footer: props => props.column.id, + cell: (info) => info.getValue(), + footer: (props) => props.column.id, }, { - accessorFn: row => row.lastName, + accessorFn: (row) => row.lastName, id: 'lastName', - cell: info => info.getValue(), + cell: (info) => info.getValue(), header: () => Last Name, - footer: props => props.column.id, + footer: (props) => props.column.id, }, ], }, { header: 'Info', - footer: props => props.column.id, + footer: (props) => props.column.id, columns: [ { accessorKey: 'age', header: () => 'Age', - footer: props => props.column.id, + footer: (props) => props.column.id, }, { header: 'More Info', @@ -48,17 +52,17 @@ function App() { { accessorKey: 'visits', header: () => Visits, - footer: props => props.column.id, + footer: (props) => props.column.id, }, { accessorKey: 'status', header: 'Status', - footer: props => props.column.id, + footer: (props) => props.column.id, }, { accessorKey: 'progress', header: 'Profile Progress', - footer: props => props.column.id, + footer: (props) => props.column.id, }, ], }, @@ -66,45 +70,42 @@ function App() { }, ] - const table = createSolidTable({ + const table = createTable({ + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + }, get data() { return data() }, columns, - state: { - get sorting() { - return sorting() - }, - }, - onSortingChange: setSorting, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), debugTable: true, }) return ( -
+
+
+ + +
- {headerGroup => ( + {(headerGroup) => ( - {header => ( + {(header) => ( - {row => ( + {(row) => ( - - {cell => ( + + {(cell) => ( )} @@ -137,11 +135,8 @@ function App() {
- {flexRender( - header.column.columnDef.header, - header.getContext() - )} + {{ asc: ' 🔼', desc: ' 🔽', @@ -120,15 +121,12 @@ function App() {
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} +
-
{table.getRowModel().rows.length} Rows
-
- -
-
{JSON.stringify(sorting(), null, 2)}
+
{table.getRowModel().rows.length.toLocaleString()} Rows
+
{JSON.stringify(table.store.state, null, 2)}
) } diff --git a/examples/solid/sorting/src/index.css b/examples/solid/sorting/src/index.css index 43c09e0f6b..b9af6ded32 100644 --- a/examples/solid/sorting/src/index.css +++ b/examples/solid/sorting/src/index.css @@ -5,6 +5,8 @@ html { table { border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; } tbody { @@ -24,3 +26,340 @@ tfoot { tfoot th { font-weight: normal; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/sorting/src/index.tsx b/examples/solid/sorting/src/index.tsx index b5618861da..ea9c952177 100644 --- a/examples/solid/sorting/src/index.tsx +++ b/examples/solid/sorting/src/index.tsx @@ -1,6 +1,5 @@ /* @refresh reload */ import { render } from 'solid-js/web' - import './index.css' import App from './App' diff --git a/examples/solid/sorting/src/makeData.ts b/examples/solid/sorting/src/makeData.ts index 331dd1eb19..b9fb014aba 100644 --- a/examples/solid/sorting/src/makeData.ts +++ b/examples/solid/sorting/src/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,13 +29,13 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] return range(len).map((d): Person => { return { ...newPerson(), diff --git a/examples/solid/sorting/tsconfig.json b/examples/solid/sorting/tsconfig.json index 7ab027b8d2..cb996fae69 100644 --- a/examples/solid/sorting/tsconfig.json +++ b/examples/solid/sorting/tsconfig.json @@ -5,8 +5,6 @@ "module": "ESNext", "lib": ["ES2020", "DOM", "DOM.Iterable"], "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, @@ -14,12 +12,10 @@ "noEmit": true, "jsx": "preserve", "jsxImportSource": "solid-js", - - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["src"] + "include": ["src", "vite.config.ts", "vite.config.js"] } diff --git a/examples/solid/sub-components/index.html b/examples/solid/sub-components/index.html new file mode 100644 index 0000000000..4d3220bdd2 --- /dev/null +++ b/examples/solid/sub-components/index.html @@ -0,0 +1,13 @@ + + + + + + Solid App + + + +
+ + + diff --git a/examples/solid/sub-components/package.json b/examples/solid/sub-components/package.json new file mode 100644 index 0000000000..e533edede5 --- /dev/null +++ b/examples/solid/sub-components/package.json @@ -0,0 +1,22 @@ +{ + "name": "tanstack-solid-table-example-sub-components", + "description": "", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ./src" + }, + "license": "MIT", + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" + }, + "dependencies": { + "@tanstack/solid-table": "^9.0.0-alpha.45", + "solid-js": "^1.9.12" + } +} diff --git a/examples/solid/sub-components/src/App.tsx b/examples/solid/sub-components/src/App.tsx new file mode 100644 index 0000000000..2f80d3fd99 --- /dev/null +++ b/examples/solid/sub-components/src/App.tsx @@ -0,0 +1,176 @@ +import { + createColumnHelper, + createExpandedRowModel, + createTable, + rowExpandingFeature, + tableFeatures, +} from '@tanstack/solid-table' +import { For, Show, createSignal } from 'solid-js' +import { makeData } from './makeData' +import type { + ColumnDef, + Row, + RowData, + TableFeatures, +} from '@tanstack/solid-table' +import type { Person } from './makeData' + +const _features = tableFeatures({ rowExpandingFeature }) + +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.display({ + id: 'expander', + header: () => null, + cell: ({ row }) => ( + 🔵}> + + + ), + }), + columnHelper.accessor('firstName', { + header: 'First Name', + cell: ({ row, getValue }) => ( +
+ {getValue()} +
+ ), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }), + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + }), + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), +]) + +type TableProps = { + data: Array + columns: Array> + renderSubComponent: (props: { row: Row }) => any + getRowCanExpand: (row: Row) => boolean +} + +function TableComponent(props: TableProps) { + const table = createTable({ + debugTable: true, + _features, + _rowModels: { + expandedRowModel: createExpandedRowModel(), + }, + columns: props.columns, + get data() { + return props.data + }, + getRowCanExpand: props.getRowCanExpand, + }) + + return ( +
+
+ + + + {(headerGroup) => ( + + + {(header) => ( + + )} + + + )} + + + + + {(row) => ( + <> + + + {(cell) => ( + + )} + + + + + + + + + )} + + +
+ +
+ +
+
+
+ +
+ {props.renderSubComponent({ row })} +
+
+
{table.getRowModel().rows.length.toLocaleString()} Rows
+
+ ) +} + +const renderSubComponent = ({ + row, +}: { + row: Row +}) => ( +
+    {JSON.stringify(row.original, null, 2)}
+  
+) + +function App() { + const [data, setData] = createSignal(makeData(20)) + const refreshData = () => setData(makeData(20)) + const stressTest = () => setData(makeData(1_000)) + + return ( + <> +
+ + +
+ true} + renderSubComponent={renderSubComponent} + /> + + ) +} + +export default App diff --git a/examples/solid/sub-components/src/index.css b/examples/solid/sub-components/src/index.css new file mode 100644 index 0000000000..40a5eb73a4 --- /dev/null +++ b/examples/solid/sub-components/src/index.css @@ -0,0 +1,360 @@ +html { + font-family: sans-serif; + font-size: 14px; +} +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} +tbody { + border-bottom: 1px solid lightgray; +} +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} +tfoot { + color: gray; +} +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/sub-components/src/index.tsx b/examples/solid/sub-components/src/index.tsx new file mode 100644 index 0000000000..ea9c952177 --- /dev/null +++ b/examples/solid/sub-components/src/index.tsx @@ -0,0 +1,6 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import './index.css' +import App from './App' + +render(() => , document.getElementById('root') as HTMLElement) diff --git a/examples/solid/sub-components/src/makeData.ts b/examples/solid/sub-components/src/makeData.ts new file mode 100644 index 0000000000..d63c724b74 --- /dev/null +++ b/examples/solid/sub-components/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/solid/sub-components/tsconfig.json b/examples/solid/sub-components/tsconfig.json new file mode 100644 index 0000000000..cb996fae69 --- /dev/null +++ b/examples/solid/sub-components/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts", "vite.config.js"] +} diff --git a/examples/solid/sub-components/vite.config.ts b/examples/solid/sub-components/vite.config.ts new file mode 100644 index 0000000000..f620d3461f --- /dev/null +++ b/examples/solid/sub-components/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import solidPlugin from 'vite-plugin-solid' +export default defineConfig({ + plugins: [solidPlugin()], + build: { target: 'esnext' }, +}) diff --git a/examples/solid/virtualized-columns/index.html b/examples/solid/virtualized-columns/index.html new file mode 100644 index 0000000000..4d3220bdd2 --- /dev/null +++ b/examples/solid/virtualized-columns/index.html @@ -0,0 +1,13 @@ + + + + + + Solid App + + + +
+ + + diff --git a/examples/solid/virtualized-columns/package.json b/examples/solid/virtualized-columns/package.json new file mode 100644 index 0000000000..1d68fea72f --- /dev/null +++ b/examples/solid/virtualized-columns/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-solid-table-example-virtualized-columns", + "description": "", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ./src" + }, + "license": "MIT", + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" + }, + "dependencies": { + "@tanstack/solid-table": "^9.0.0-alpha.45", + "@tanstack/solid-virtual": "^3.13.24", + "solid-js": "^1.9.12" + } +} diff --git a/examples/solid/virtualized-columns/src/App.tsx b/examples/solid/virtualized-columns/src/App.tsx new file mode 100644 index 0000000000..84810eea6c --- /dev/null +++ b/examples/solid/virtualized-columns/src/App.tsx @@ -0,0 +1,335 @@ +import { + FlexRender, + columnSizingFeature, + columnVisibilityFeature, + createSortedRowModel, + createTable, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/solid-table' +import { createVirtualizer } from '@tanstack/solid-virtual' +import { For, createSignal } from 'solid-js' +import { makeColumns, makeData } from './makeData' +import type { + Cell, + Header, + HeaderGroup, + Row, + SolidTable, +} from '@tanstack/solid-table' +import type { VirtualItem, Virtualizer } from '@tanstack/solid-virtual' +import type { Person } from './makeData' + +const _features = tableFeatures({ + columnSizingFeature, + columnVisibilityFeature, + rowSortingFeature, +}) + +function App() { + const columns = makeColumns(1_000) + const [data, setData] = createSignal(makeData(1_000, columns)) + + const refreshData = () => setData(makeData(1_000, columns)) + const stressTest = () => setData(makeData(10_000, columns)) + + const table = createTable({ + _features, + _rowModels: { sortedRowModel: createSortedRowModel(sortFns) }, + columns, + get data() { + return data() + }, + debugTable: true, + }) + + return ( +
+
+ + +
+
({columns.length.toLocaleString()} columns)
+
({data().length.toLocaleString()} rows)
+ +
+ ) +} + +// Important: Keep both virtualizers and the scroll container ref in the same component. +// The ref must be undefined when createVirtualizer runs (before JSX return), +// so that onMount can set up scroll observers after the element is in the DOM. +function TableContainer(props: { + table: SolidTable +}) { + const visibleColumns = () => props.table.getVisibleLeafColumns() + const rows = () => props.table.getRowModel().rows + + let tableContainerRef: HTMLDivElement | undefined + + // We are using a slightly different virtualization strategy for columns (compared to virtual rows) + // in order to support dynamic row heights. + const columnVirtualizer = createVirtualizer< + HTMLDivElement, + HTMLTableCellElement + >({ + get count() { + return visibleColumns().length + }, + estimateSize: (index) => visibleColumns()[index].getSize(), // estimate width of each column for accurate scrollbar dragging + getScrollElement: () => tableContainerRef ?? null, + horizontal: true, + overscan: 3, // how many columns to render on each side off screen (adjust this for performance) + }) + + // dynamic row height virtualization - alternatively you could use a simpler fixed row height strategy without `measureElement` + const rowVirtualizer = createVirtualizer( + { + get count() { + return rows().length + }, + estimateSize: () => 33, // estimate row height for accurate scrollbar dragging + getScrollElement: () => tableContainerRef ?? null, + // measure dynamic row height, except in firefox because it measures table border height incorrectly + measureElement: + typeof window !== 'undefined' && + navigator.userAgent.indexOf('Firefox') === -1 + ? (element) => element.getBoundingClientRect().height + : undefined, + overscan: 5, + }, + ) + + // Different virtualization strategy for columns - instead of absolute and translateY, + // we add empty columns to the left and right + const virtualPaddingLeft = () => { + const vcs = columnVirtualizer.getVirtualItems() + return vcs.length ? (vcs[0]?.start ?? 0) : undefined + } + + const virtualPaddingRight = () => { + const vcs = columnVirtualizer.getVirtualItems() + if (!vcs.length) return undefined + return columnVirtualizer.getTotalSize() - (vcs[vcs.length - 1]?.end ?? 0) + } + + return ( +
+ {/* Even though we're still using semantic table tags, we must use CSS grid and flexbox for dynamic row heights */} + + + +
+
+ ) +} + +function TableHead(props: { + columnVirtualizer: Virtualizer + table: SolidTable + virtualPaddingLeft: number | undefined + virtualPaddingRight: number | undefined +}) { + return ( + + + {(headerGroup) => ( + + )} + + + ) +} + +function TableHeadRow(props: { + columnVirtualizer: Virtualizer + headerGroup: HeaderGroup + virtualPaddingLeft: number | undefined + virtualPaddingRight: number | undefined + table: SolidTable +}) { + const virtualColumns = () => props.columnVirtualizer.getVirtualItems() + return ( + + {props.virtualPaddingLeft ? ( + // fake empty column to the left for virtualization scroll padding + + ) : null} + + {(virtualColumn) => { + const header = props.headerGroup.headers[virtualColumn.index] + return + }} + + {props.virtualPaddingRight ? ( + // fake empty column to the right for virtualization scroll padding + + ) : null} + + ) +} + +function TableHeadCell(props: { + header: Header + table: SolidTable +}) { + return ( + +
+ + {( + { + asc: ' 🔼', + desc: ' 🔽', + } as Record + )[props.header.column.getIsSorted() as string] ?? null} +
+ + ) +} + +function TableBody(props: { + columnVirtualizer: Virtualizer + rowVirtualizer: Virtualizer + rows: () => Array> + table: SolidTable + virtualPaddingLeft: number | undefined + virtualPaddingRight: number | undefined +}) { + const virtualRows = () => props.rowVirtualizer.getVirtualItems() + + return ( + + + {(virtualRow) => { + const row = props.rows()[virtualRow.index] + return ( + + ) + }} + + + ) +} + +function TableBodyRow(props: { + columnVirtualizer: Virtualizer + row: Row + rowVirtualizer: Virtualizer + virtualPaddingLeft: number | undefined + virtualPaddingRight: number | undefined + virtualRow: VirtualItem + table: SolidTable +}) { + const visibleCells = () => props.row.getVisibleCells() + const virtualColumns = () => props.columnVirtualizer.getVirtualItems() + return ( + props.rowVirtualizer.measureElement(node)} // measure dynamic row height + style={{ + display: 'flex', + position: 'absolute', + transform: `translateY(${props.virtualRow.start}px)`, // this should always be a `style` as it changes on scroll + width: '100%', + }} + > + {props.virtualPaddingLeft ? ( + // fake empty column to the left for virtualization scroll padding + + ) : null} + + {(vc) => { + const cell = visibleCells()[vc.index] + return + }} + + {props.virtualPaddingRight ? ( + // fake empty column to the right for virtualization scroll padding + + ) : null} + + ) +} + +function TableBodyCell(props: { + cell: Cell + table: SolidTable +}) { + return ( + + + + ) +} + +export default App diff --git a/examples/solid/virtualized-columns/src/index.css b/examples/solid/virtualized-columns/src/index.css new file mode 100644 index 0000000000..720064ab9d --- /dev/null +++ b/examples/solid/virtualized-columns/src/index.css @@ -0,0 +1,377 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border-collapse: collapse; + border-spacing: 0; + font-family: arial, sans-serif; + table-layout: fixed; +} + +thead { + background: lightgray; +} + +tr { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; + text-align: left; +} + +td { + padding: 6px; +} + +.container { + border: 1px solid lightgray; + margin: 1rem auto; +} + +.app { + margin: 1rem auto; + text-align: center; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/virtualized-columns/src/index.tsx b/examples/solid/virtualized-columns/src/index.tsx new file mode 100644 index 0000000000..ea9c952177 --- /dev/null +++ b/examples/solid/virtualized-columns/src/index.tsx @@ -0,0 +1,6 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import './index.css' +import App from './App' + +render(() => , document.getElementById('root') as HTMLElement) diff --git a/examples/solid/virtualized-columns/src/makeData.ts b/examples/solid/virtualized-columns/src/makeData.ts new file mode 100644 index 0000000000..837114e211 --- /dev/null +++ b/examples/solid/virtualized-columns/src/makeData.ts @@ -0,0 +1,17 @@ +import { faker } from '@faker-js/faker' + +export const makeColumns = (num: number) => + [...Array(num)].map((_, i) => ({ + accessorKey: i.toString(), + header: 'Column ' + i.toString(), + size: Math.floor(Math.random() * 150) + 100, + })) + +export const makeData = (num: number, columns: Array) => + [...Array(num)].map(() => ({ + ...Object.fromEntries( + columns.map((col: any) => [col.accessorKey, faker.person.firstName()]), + ), + })) + +export type Person = ReturnType[0] diff --git a/examples/solid/virtualized-columns/tsconfig.json b/examples/solid/virtualized-columns/tsconfig.json new file mode 100644 index 0000000000..cb996fae69 --- /dev/null +++ b/examples/solid/virtualized-columns/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts", "vite.config.js"] +} diff --git a/examples/solid/virtualized-columns/vite.config.ts b/examples/solid/virtualized-columns/vite.config.ts new file mode 100644 index 0000000000..bd4f9faf75 --- /dev/null +++ b/examples/solid/virtualized-columns/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import solidPlugin from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solidPlugin()], + build: { target: 'esnext' }, +}) diff --git a/examples/solid/virtualized-infinite-scrolling/index.html b/examples/solid/virtualized-infinite-scrolling/index.html new file mode 100644 index 0000000000..938a04d252 --- /dev/null +++ b/examples/solid/virtualized-infinite-scrolling/index.html @@ -0,0 +1,14 @@ + + + + + + + Solid App + + + +
+ + + diff --git a/examples/solid/virtualized-infinite-scrolling/package.json b/examples/solid/virtualized-infinite-scrolling/package.json new file mode 100644 index 0000000000..0c53e125be --- /dev/null +++ b/examples/solid/virtualized-infinite-scrolling/package.json @@ -0,0 +1,25 @@ +{ + "name": "tanstack-solid-table-example-virtualized-infinite-scrolling", + "description": "", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ./src" + }, + "license": "MIT", + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" + }, + "dependencies": { + "@tanstack/solid-query": "^5.100.9", + "@tanstack/solid-store": "^0.11.0", + "@tanstack/solid-table": "^9.0.0-alpha.45", + "@tanstack/solid-virtual": "^3.13.24", + "solid-js": "^1.9.12" + } +} diff --git a/examples/solid/virtualized-infinite-scrolling/src/App.tsx b/examples/solid/virtualized-infinite-scrolling/src/App.tsx new file mode 100644 index 0000000000..4f7c32365f --- /dev/null +++ b/examples/solid/virtualized-infinite-scrolling/src/App.tsx @@ -0,0 +1,252 @@ +import { + FlexRender, + columnSizingFeature, + createColumnHelper, + createSortedRowModel, + createTable, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/solid-table' +import { keepPreviousData, useInfiniteQuery } from '@tanstack/solid-query' +import { createAtom, useSelector } from '@tanstack/solid-store' +import { createVirtualizer } from '@tanstack/solid-virtual' +import { For, Show, createMemo, onMount } from 'solid-js' +import { fetchData } from './makeData' +import type { Person, PersonApiResponse } from './makeData' +import type { SortingState } from '@tanstack/solid-table' +import type { Virtualizer } from '@tanstack/solid-virtual' + +const fetchSize = 50 + +const _features = tableFeatures({ columnSizingFeature, rowSortingFeature }) + +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.accessor('id', { + header: 'ID', + size: 60, + }), + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + }), + columnHelper.accessor('age', { + header: () => 'Age', + size: 50, + }), + columnHelper.accessor('visits', { + header: () => Visits, + size: 50, + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + size: 80, + }), + columnHelper.accessor('createdAt', { + header: 'Created At', + cell: (info) => info.getValue().toLocaleString(), + size: 200, + }), +]) + +function App() { + let tableContainerRef: HTMLDivElement | undefined + + const sortingAtom = createAtom([]) + const sorting = useSelector(sortingAtom) + + const query = useInfiniteQuery(() => ({ + queryKey: ['people', sorting()], + queryFn: async ({ pageParam = 0 }) => { + const start = (pageParam as number) * fetchSize + return fetchData(start, fetchSize, sorting()) + }, + initialPageParam: 0, + getNextPageParam: ( + _lastGroup: PersonApiResponse, + groups: Array, + ) => groups.length, + refetchOnWindowFocus: false, + placeholderData: keepPreviousData, + })) + + const flatData = createMemo( + () => query.data?.pages.flatMap((page) => page.data) ?? [], + ) + const totalDBRowCount = () => query.data?.pages[0]?.meta?.totalRowCount ?? 0 + const totalFetched = () => flatData().length + + const fetchMoreOnBottomReached = ( + containerRefElement?: HTMLDivElement | null, + ) => { + if (containerRefElement) { + const { scrollHeight, scrollTop, clientHeight } = containerRefElement + if ( + scrollHeight - scrollTop - clientHeight < 500 && + !query.isFetching && + totalFetched() < totalDBRowCount() + ) { + void query.fetchNextPage() + } + } + } + + // Check on mount to see if the table is already scrolled to the bottom and immediately needs to fetch more data + onMount(() => { + fetchMoreOnBottomReached(tableContainerRef) + }) + + const table = createTable({ + _features, + _rowModels: { sortedRowModel: createSortedRowModel(sortFns) }, + get data() { + return flatData() + }, + columns, + atoms: { + sorting: sortingAtom, + }, + manualSorting: true, + debugTable: true, + }) + + const rows = () => table.getRowModel().rows + + // Important: The virtualizer and the scroll container ref must be in the same + // component scope, and NOT inside a wrapper. creates a reactive + // boundary that disrupts the virtualizer's onMount timing. + const rowVirtualizer = createVirtualizer( + { + get count() { + return rows().length + }, + estimateSize: () => 33, + getScrollElement: () => tableContainerRef ?? null, + measureElement: + typeof window !== 'undefined' && + navigator.userAgent.indexOf('Firefox') === -1 + ? (element) => element.getBoundingClientRect().height + : undefined, + overscan: 5, + }, + ) + + return ( +
+ +

+ Notice: You are currently running Solid in + development mode. Virtualized rendering performance will be slightly + degraded until this application is built for production. +

+
+ ({totalFetched().toLocaleString()} of {totalDBRowCount().toLocaleString()}{' '} + rows fetched) +
fetchMoreOnBottomReached(e.currentTarget)} + ref={tableContainerRef} + style={{ + overflow: 'auto', + position: 'relative', + height: '600px', + }} + > + + + + {(headerGroup) => ( + + + {(header) => ( + + )} + + + )} + + + + + {(virtualRow) => { + const row = rows()[virtualRow.index] + return ( + rowVirtualizer.measureElement(node)} + style={{ + display: 'flex', + position: 'absolute', + transform: `translateY(${virtualRow.start}px)`, + width: '100%', + }} + > + + {(cell) => ( + + )} + + + ) + }} + + +
+
+ + {( + { + asc: ' 🔼', + desc: ' 🔽', + } as Record + )[header.column.getIsSorted() as string] ?? null} +
+
+ +
+
+ +
Fetching More...
+
+
+ ) +} + +export default App diff --git a/examples/solid/virtualized-infinite-scrolling/src/index.css b/examples/solid/virtualized-infinite-scrolling/src/index.css new file mode 100644 index 0000000000..720064ab9d --- /dev/null +++ b/examples/solid/virtualized-infinite-scrolling/src/index.css @@ -0,0 +1,377 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border-collapse: collapse; + border-spacing: 0; + font-family: arial, sans-serif; + table-layout: fixed; +} + +thead { + background: lightgray; +} + +tr { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; + text-align: left; +} + +td { + padding: 6px; +} + +.container { + border: 1px solid lightgray; + margin: 1rem auto; +} + +.app { + margin: 1rem auto; + text-align: center; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/virtualized-infinite-scrolling/src/index.tsx b/examples/solid/virtualized-infinite-scrolling/src/index.tsx new file mode 100644 index 0000000000..d6ae0f7cdd --- /dev/null +++ b/examples/solid/virtualized-infinite-scrolling/src/index.tsx @@ -0,0 +1,16 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import { QueryClient, QueryClientProvider } from '@tanstack/solid-query' +import './index.css' +import App from './App' + +const queryClient = new QueryClient() + +render( + () => ( + + + + ), + document.getElementById('root') as HTMLElement, +) diff --git a/examples/solid/virtualized-infinite-scrolling/src/makeData.ts b/examples/solid/virtualized-infinite-scrolling/src/makeData.ts new file mode 100644 index 0000000000..cdd2599a98 --- /dev/null +++ b/examples/solid/virtualized-infinite-scrolling/src/makeData.ts @@ -0,0 +1,89 @@ +import { faker } from '@faker-js/faker' +import type { SortingState } from '@tanstack/solid-table' + +export type Person = { + id: number + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + createdAt: Date +} + +export type PersonApiResponse = { + data: Array + meta: { + totalRowCount: number + } +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (index: number): Person => { + return { + id: index + 1, + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + createdAt: faker.date.anytime(), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((d): Person => { + return { + ...newPerson(d), + } + }) + } + + return makeDataLevel() +} + +const data = makeData(1000) + +// simulates a backend api +export const fetchData = async ( + start: number, + size: number, + sorting: SortingState, +) => { + const dbData = [...data] + if (sorting.length) { + const sort = sorting[0] + const { id, desc } = sort as { id: keyof Person; desc: boolean } + dbData.sort((a, b) => { + if (desc) { + return a[id] < b[id] ? 1 : -1 + } + return a[id] > b[id] ? 1 : -1 + }) + } + + // simulate a backend api + await new Promise((resolve) => setTimeout(resolve, 200)) + + return { + data: dbData.slice(start, start + size), + meta: { + totalRowCount: dbData.length, + }, + } +} diff --git a/examples/solid/virtualized-infinite-scrolling/tsconfig.json b/examples/solid/virtualized-infinite-scrolling/tsconfig.json new file mode 100644 index 0000000000..cb996fae69 --- /dev/null +++ b/examples/solid/virtualized-infinite-scrolling/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts", "vite.config.js"] +} diff --git a/examples/solid/virtualized-infinite-scrolling/vite.config.ts b/examples/solid/virtualized-infinite-scrolling/vite.config.ts new file mode 100644 index 0000000000..b80a99b402 --- /dev/null +++ b/examples/solid/virtualized-infinite-scrolling/vite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite' +import solidPlugin from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solidPlugin()], + build: { + target: 'esnext', + }, +}) diff --git a/examples/solid/virtualized-rows/index.html b/examples/solid/virtualized-rows/index.html new file mode 100644 index 0000000000..4d3220bdd2 --- /dev/null +++ b/examples/solid/virtualized-rows/index.html @@ -0,0 +1,13 @@ + + + + + + Solid App + + + +
+ + + diff --git a/examples/solid/virtualized-rows/package.json b/examples/solid/virtualized-rows/package.json new file mode 100644 index 0000000000..6afc74d8bb --- /dev/null +++ b/examples/solid/virtualized-rows/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-solid-table-example-virtualized-rows", + "description": "", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ./src" + }, + "license": "MIT", + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" + }, + "dependencies": { + "@tanstack/solid-table": "^9.0.0-alpha.45", + "@tanstack/solid-virtual": "^3.13.24", + "solid-js": "^1.9.12" + } +} diff --git a/examples/solid/virtualized-rows/src/App.tsx b/examples/solid/virtualized-rows/src/App.tsx new file mode 100644 index 0000000000..e6701e343c --- /dev/null +++ b/examples/solid/virtualized-rows/src/App.tsx @@ -0,0 +1,234 @@ +import { + FlexRender, + columnSizingFeature, + createSortedRowModel, + createTable, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/solid-table' +import { createVirtualizer } from '@tanstack/solid-virtual' +import { For, createSignal } from 'solid-js' +import { makeData } from './makeData' +import type { Row, SolidTable } from '@tanstack/solid-table' +import type { VirtualItem, Virtualizer } from '@tanstack/solid-virtual' +import type { Person } from './makeData' + +const _features = tableFeatures({ columnSizingFeature, rowSortingFeature }) + +// This is a dynamic row height example, which is more complicated, but allows for a more realistic table. +// See https://tanstack.com/virtual/v3/docs/examples/solid/table for a simpler fixed row height example. +function App() { + const columns = [ + { + accessorKey: 'id', + header: 'ID', + size: 60, + }, + { + accessorKey: 'firstName', + cell: (info: any) => info.getValue(), + }, + { + accessorFn: (row: Person) => row.lastName, + id: 'lastName', + cell: (info: any) => info.getValue(), + header: () => Last Name, + }, + { + accessorKey: 'age', + header: () => 'Age', + size: 50, + }, + { + accessorKey: 'visits', + header: () => Visits, + size: 50, + }, + { + accessorKey: 'status', + header: 'Status', + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + size: 80, + }, + { + accessorKey: 'createdAt', + header: 'Created At', + cell: (info: any) => info.getValue().toLocaleString(), + size: 250, + }, + ] + + const [data, setData] = createSignal(makeData(200_000)) + + const refreshData = () => setData(makeData(200_000)) + const stressTest = () => setData(makeData(1_000_000)) + + const table = createTable({ + _features, + _rowModels: { sortedRowModel: createSortedRowModel(sortFns) }, + columns, + get data() { + return data() + }, + debugTable: true, + }) + + return ( + <> +
+
+ + +
+ ({data().length.toLocaleString()} rows) + +
+
{JSON.stringify(table.store.state, null, 2)}
+ + ) +} + +// Important: Keep the virtualizer and the scroll container ref in the same component. +// The ref must be undefined when createVirtualizer runs (before JSX return), +// so that onMount can set up scroll observers after the element is in the DOM. +function VirtualizedTable(props: { + table: SolidTable +}) { + let tableContainerRef: HTMLDivElement | undefined + + const rows = () => props.table.getRowModel().rows + + // Important: Keep the row virtualizer in the lowest component possible to avoid unnecessary re-renders. + const rowVirtualizer = createVirtualizer( + { + get count() { + return rows().length + }, + estimateSize: () => 33, // estimate row height for accurate scrollbar dragging + getScrollElement: () => tableContainerRef ?? null, + // measure dynamic row height, except in firefox because it measures table border height incorrectly + measureElement: + typeof window !== 'undefined' && + navigator.userAgent.indexOf('Firefox') === -1 + ? (element) => element.getBoundingClientRect().height + : undefined, + overscan: 5, + }, + ) + + return ( +
+ {/* Even though we're still using semantic table tags, we must use CSS grid and flexbox for dynamic row heights */} + + + + {(headerGroup) => ( + + + {(header) => ( + + )} + + + )} + + + + + {(virtualRow) => { + const row = rows()[virtualRow.index] + return ( + + ) + }} + + +
+
+ + {( + { + asc: ' 🔼', + desc: ' 🔽', + } as Record + )[header.column.getIsSorted() as string] ?? null} +
+
+
+ ) +} + +function TableBodyRow(props: { + row: Row + virtualRow: VirtualItem + rowVirtualizer: Virtualizer + table: SolidTable +}) { + return ( + props.rowVirtualizer.measureElement(node)} // measure dynamic row height + style={{ + display: 'flex', + position: 'absolute', + transform: `translateY(${props.virtualRow.start}px)`, // this should always be a `style` as it changes on scroll + width: '100%', + }} + > + + {(cell) => ( + + + + )} + + + ) +} + +export default App diff --git a/examples/solid/virtualized-rows/src/index.css b/examples/solid/virtualized-rows/src/index.css new file mode 100644 index 0000000000..720064ab9d --- /dev/null +++ b/examples/solid/virtualized-rows/src/index.css @@ -0,0 +1,377 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border-collapse: collapse; + border-spacing: 0; + font-family: arial, sans-serif; + table-layout: fixed; +} + +thead { + background: lightgray; +} + +tr { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; + text-align: left; +} + +td { + padding: 6px; +} + +.container { + border: 1px solid lightgray; + margin: 1rem auto; +} + +.app { + margin: 1rem auto; + text-align: center; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/virtualized-rows/src/index.tsx b/examples/solid/virtualized-rows/src/index.tsx new file mode 100644 index 0000000000..ea9c952177 --- /dev/null +++ b/examples/solid/virtualized-rows/src/index.tsx @@ -0,0 +1,6 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import './index.css' +import App from './App' + +render(() => , document.getElementById('root') as HTMLElement) diff --git a/examples/solid/virtualized-rows/src/makeData.ts b/examples/solid/virtualized-rows/src/makeData.ts new file mode 100644 index 0000000000..acd0e474d9 --- /dev/null +++ b/examples/solid/virtualized-rows/src/makeData.ts @@ -0,0 +1,41 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + id: number + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + createdAt: Date +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (index: number): Person => ({ + id: index + 1, + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + createdAt: faker.date.anytime(), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((d): Person => ({ ...newPerson(d) })) + } + return makeDataLevel() +} diff --git a/examples/solid/virtualized-rows/tsconfig.json b/examples/solid/virtualized-rows/tsconfig.json new file mode 100644 index 0000000000..cb996fae69 --- /dev/null +++ b/examples/solid/virtualized-rows/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts", "vite.config.js"] +} diff --git a/examples/solid/virtualized-rows/vite.config.ts b/examples/solid/virtualized-rows/vite.config.ts new file mode 100644 index 0000000000..bd4f9faf75 --- /dev/null +++ b/examples/solid/virtualized-rows/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import solidPlugin from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solidPlugin()], + build: { target: 'esnext' }, +}) diff --git a/examples/solid/with-tanstack-form/index.html b/examples/solid/with-tanstack-form/index.html new file mode 100644 index 0000000000..938a04d252 --- /dev/null +++ b/examples/solid/with-tanstack-form/index.html @@ -0,0 +1,14 @@ + + + + + + + Solid App + + + +
+ + + diff --git a/examples/solid/with-tanstack-form/package.json b/examples/solid/with-tanstack-form/package.json new file mode 100644 index 0000000000..e54d6372e0 --- /dev/null +++ b/examples/solid/with-tanstack-form/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-solid-table-example-with-tanstack-form", + "description": "", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ./src" + }, + "license": "MIT", + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" + }, + "dependencies": { + "@tanstack/solid-form": "^1.31.0", + "@tanstack/solid-table": "^9.0.0-alpha.45", + "solid-js": "^1.9.12", + "zod": "^4.4.3" + } +} diff --git a/examples/solid/with-tanstack-form/src/App.tsx b/examples/solid/with-tanstack-form/src/App.tsx new file mode 100644 index 0000000000..0a00f1a8fb --- /dev/null +++ b/examples/solid/with-tanstack-form/src/App.tsx @@ -0,0 +1,409 @@ +import { + columnFilteringFeature, + createColumnHelper, + createFilteredRowModel, + createPaginatedRowModel, + createTable, + filterFns, + rowPaginationFeature, + tableFeatures, +} from '@tanstack/solid-table' +import { For, Show, createMemo } from 'solid-js' +import { z } from 'zod' +import { makeData } from './makeData' +import { useAppForm } from './form' +import type { Column, Table } from '@tanstack/solid-table' +import type { Person } from './makeData' + +// Define table features +const _features = tableFeatures({ + rowPaginationFeature, + columnFilteringFeature, +}) + +// Create column helper with features and Person type +const columnHelper = createColumnHelper() + +// Zod validation schema for a person +const personSchema = z.object({ + firstName: z.string().min(1, 'First name is required'), + lastName: z.string().min(1, 'Last name is required'), + age: z + .number() + .min(0, 'Age must be positive') + .max(150, 'Age must be realistic'), + visits: z.number().min(0, 'Visits must be positive'), + progress: z + .number() + .min(0, 'Progress must be 0-100') + .max(100, 'Progress must be 0-100'), + status: z.enum(['relationship', 'complicated', 'single']), +}) + +// Form data schema +const formSchema = z.object({ + data: z.array(personSchema), +}) + +type FormData = z.infer + +function App() { + // Initialize form with makeData + const form = useAppForm(() => ({ + defaultValues: { + data: makeData(100), + }, + onSubmit: ({ value }: { value: FormData }) => { + alert( + `Submitted ${value.data.length} records!\n\nFirst record: ${JSON.stringify(value.data[0], null, 2)}`, + ) + }, + validators: { + onChange: formSchema, + }, + })) + + // Create columns with form fields for editing + // Use createMemo since columns depend on `form` (which is reactive) + const columns = createMemo(() => + columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + footer: (props) => props.column.id, + cell: ({ row }) => ( + + {(field) => } + + ), + }), + columnHelper.accessor('lastName', { + header: () => Last Name, + footer: (props) => props.column.id, + cell: ({ row }) => ( + + {(field) => } + + ), + }), + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + cell: ({ row }) => ( + + {(field) => } + + ), + }), + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + cell: ({ row }) => ( + + {(field) => } + + ), + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + cell: ({ row }) => ( + + {(field) => } + + ), + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + cell: ({ row }) => ( + + {(field) => } + + ), + }), + ]), + ) + + // Create table using form state as data source + const table = createTable({ + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + }, + get columns() { + return columns() + }, + get data() { + return form.state.values.data + }, + debugTable: true, + }) + + const refreshData = () => { + form.reset({ data: makeData(100) }) + } + + const stressTest = () => { + form.reset({ data: makeData(200_000) }) + } + + const addRow = () => { + form.pushFieldValue('data', { + firstName: '', + lastName: '', + age: 0, + visits: 0, + progress: 0, + status: 'single', + }) + } + + return ( +
+
+ + +
+
{ + e.preventDefault() + e.stopPropagation() + void form.handleSubmit() + }} + > + {/* Form state indicators */} +
+ + + + + + + + +
+ + {/* Table */} +
+
+ + + + {(headerGroup) => ( + + + {(header) => ( + + )} + + + )} + + + + + {(row) => ( + + + {(cell) => ( + + )} + + + )} + + +
+ +
+ + +
+ +
+
+
+
+
+ +
+ + {/* Pagination controls */} +
+
+ + + + + +
Page
+ + {(table.store.state.pagination.pageIndex + 1).toLocaleString()}{' '} + of {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = e.currentTarget.value + ? Number(e.currentTarget.value) - 1 + : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
+
+ Showing {table.getRowModel().rows.length.toLocaleString()} of{' '} + {table.getRowCount().toLocaleString()} Rows +
+
+ +
+ ) +} + +function Filter(props: { + column: Column + table: Table +}) { + const firstValue = () => + props.table.getPreFilteredRowModel().flatRows[0]?.getValue(props.column.id) + + const columnFilterValue = () => props.column.getFilterValue() + + return ( + props.column.setFilterValue(e.currentTarget.value)} + placeholder="Search..." + class="filter-select" + /> + } + > +
+ + props.column.setFilterValue((old: [number, number]) => [ + e.currentTarget.value, + old?.[1], + ]) + } + placeholder="Min" + class="filter-input" + /> + + props.column.setFilterValue((old: [number, number]) => [ + old?.[0], + e.currentTarget.value, + ]) + } + placeholder="Max" + class="filter-input" + /> +
+
+ ) +} + +export default App diff --git a/examples/solid/with-tanstack-form/src/form.tsx b/examples/solid/with-tanstack-form/src/form.tsx new file mode 100644 index 0000000000..9197812da5 --- /dev/null +++ b/examples/solid/with-tanstack-form/src/form.tsx @@ -0,0 +1,126 @@ +import { + createFormHook, + createFormHookContexts, + useStore, +} from '@tanstack/solid-form' +import { For, Show } from 'solid-js' + +// Create form and field contexts +export const { fieldContext, useFieldContext, formContext, useFormContext } = + createFormHookContexts() + +// TextField component for string inputs +function TextField() { + const field = useFieldContext() + const errors = useStore(field().store, (state) => state.meta.errors) + + return ( +
+ field().handleChange(e.currentTarget.value)} + onBlur={() => field().handleBlur()} + /> + 0}> +
{errors().join(', ')}
+
+
+ ) +} + +// NumberField component for numeric inputs +function NumberField() { + const field = useFieldContext() + const errors = useStore(field().store, (state) => state.meta.errors) + + return ( +
+ field().handleChange(Number(e.currentTarget.value))} + onBlur={() => field().handleBlur()} + /> + 0}> +
{errors().join(', ')}
+
+
+ ) +} + +// SelectField component for status dropdown +const statusOptions = ['relationship', 'complicated', 'single'] as const + +function SelectField() { + const field = useFieldContext() + const errors = useStore(field().store, (state) => state.meta.errors) + + return ( +
+ + 0}> +
{errors().join(', ')}
+
+
+ ) +} + +// SubmitButton component that shows form state +function SubmitButton(props: { label: string }) { + const form = useFormContext() + return ( + + ) +} + +// FormStateIndicator component to show dirty/valid state +function FormStateIndicator() { + const form = useFormContext() + return ( +
+ + {form.state.isDirty ? '● Modified' : '○ Pristine'} + + + {form.state.isValid ? '✓ Valid' : '✗ Invalid'} + + 0}> + + Errors: {JSON.stringify(form.state.errorMap)} + + +
+ ) +} + +// Create the form hook with all components +export const { useAppForm } = createFormHook({ + fieldComponents: { + TextField, + NumberField, + SelectField, + }, + formComponents: { + SubmitButton, + FormStateIndicator, + }, + fieldContext, + formContext, +}) diff --git a/examples/solid/with-tanstack-form/src/index.css b/examples/solid/with-tanstack-form/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/solid/with-tanstack-form/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/with-tanstack-form/src/index.tsx b/examples/solid/with-tanstack-form/src/index.tsx new file mode 100644 index 0000000000..ea9c952177 --- /dev/null +++ b/examples/solid/with-tanstack-form/src/index.tsx @@ -0,0 +1,6 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import './index.css' +import App from './App' + +render(() => , document.getElementById('root') as HTMLElement) diff --git a/examples/solid/with-tanstack-form/src/makeData.ts b/examples/solid/with-tanstack-form/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/solid/with-tanstack-form/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/solid/with-tanstack-form/tsconfig.json b/examples/solid/with-tanstack-form/tsconfig.json new file mode 100644 index 0000000000..cb996fae69 --- /dev/null +++ b/examples/solid/with-tanstack-form/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts", "vite.config.js"] +} diff --git a/examples/solid/with-tanstack-form/vite.config.ts b/examples/solid/with-tanstack-form/vite.config.ts new file mode 100644 index 0000000000..b80a99b402 --- /dev/null +++ b/examples/solid/with-tanstack-form/vite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite' +import solidPlugin from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solidPlugin()], + build: { + target: 'esnext', + }, +}) diff --git a/examples/solid/with-tanstack-query/.gitignore b/examples/solid/with-tanstack-query/.gitignore new file mode 100644 index 0000000000..76add878f8 --- /dev/null +++ b/examples/solid/with-tanstack-query/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist \ No newline at end of file diff --git a/examples/solid/with-tanstack-query/README.md b/examples/solid/with-tanstack-query/README.md new file mode 100644 index 0000000000..b168d3c4b1 --- /dev/null +++ b/examples/solid/with-tanstack-query/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run start` or `yarn start` diff --git a/examples/solid/with-tanstack-query/index.html b/examples/solid/with-tanstack-query/index.html new file mode 100644 index 0000000000..7d9b4f7c51 --- /dev/null +++ b/examples/solid/with-tanstack-query/index.html @@ -0,0 +1,16 @@ + + + + + + + + Solid App + + + +
+ + + + diff --git a/examples/solid/with-tanstack-query/package.json b/examples/solid/with-tanstack-query/package.json new file mode 100644 index 0000000000..d938771d14 --- /dev/null +++ b/examples/solid/with-tanstack-query/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-solid-table-example-with-tanstack-query", + "description": "", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ./src" + }, + "license": "MIT", + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" + }, + "dependencies": { + "@tanstack/solid-query": "^5.100.9", + "@tanstack/solid-store": "^0.11.0", + "@tanstack/solid-table": "^9.0.0-alpha.45", + "solid-js": "^1.9.12" + } +} diff --git a/examples/solid/with-tanstack-query/src/App.tsx b/examples/solid/with-tanstack-query/src/App.tsx new file mode 100644 index 0000000000..6192a6cf92 --- /dev/null +++ b/examples/solid/with-tanstack-query/src/App.tsx @@ -0,0 +1,187 @@ +import { keepPreviousData, useQuery } from '@tanstack/solid-query' +import { createAtom, useSelector } from '@tanstack/solid-store' +import { + FlexRender, + createColumnHelper, + createTable, + rowPaginationFeature, + tableFeatures, +} from '@tanstack/solid-table' +import { For } from 'solid-js' +import { fetchData } from './fetchData' +import type { PaginationState } from '@tanstack/solid-table' +import type { Person } from './fetchData' + +const _features = tableFeatures({ + rowPaginationFeature, +}) + +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('lastName', { + header: 'Last Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('age', { + header: 'Age', + }), + columnHelper.accessor('visits', { + header: 'Visits', + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + }), +]) + +function App() { + const paginationAtom = createAtom({ + pageIndex: 0, + pageSize: 10, + }) + const pagination = useSelector(paginationAtom) + + const dataQuery = useQuery(() => ({ + queryKey: ['data', pagination()], + queryFn: () => fetchData(pagination()), + placeholderData: keepPreviousData, + })) + + const defaultData: Array = [] + + const table = createTable({ + _features, + _rowModels: {}, + columns, + get data() { + return dataQuery.data?.rows ?? defaultData + }, + get rowCount() { + return dataQuery.data?.rowCount + }, + atoms: { + pagination: paginationAtom, + }, + manualPagination: true, + debugTable: true, + }) + + return ( +
+
+ + + + {(headerGroup) => ( + + + {(header) => ( + + )} + + + )} + + + + + {(row) => ( + + + {(cell) => ( + + )} + + + )} + + +
+ {header.isPlaceholder ? null : ( + + )} +
+ +
+
+
+ + + + + +
Page
+ + {(pagination().pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = e.currentTarget.value + ? Number(e.currentTarget.value) - 1 + : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + + {dataQuery.isFetching ? 'Loading...' : null} +
+
+ Showing {table.getRowModel().rows.length.toLocaleString()} of{' '} + {dataQuery.data?.rowCount.toLocaleString()} Rows +
+
{JSON.stringify(pagination(), null, 2)}
+
+ ) +} + +export default App diff --git a/examples/solid/with-tanstack-query/src/fetchData.ts b/examples/solid/with-tanstack-query/src/fetchData.ts new file mode 100644 index 0000000000..0ad745b375 --- /dev/null +++ b/examples/solid/with-tanstack-query/src/fetchData.ts @@ -0,0 +1,66 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} + +const data = makeData(10000) + +export async function fetchData(options: { + pageIndex: number + pageSize: number +}) { + await new Promise((resolve) => setTimeout(resolve, 500)) + + return { + rows: data.slice( + options.pageIndex * options.pageSize, + (options.pageIndex + 1) * options.pageSize, + ), + pageCount: Math.ceil(data.length / options.pageSize), + rowCount: data.length, + } +} diff --git a/examples/solid/with-tanstack-query/src/index.css b/examples/solid/with-tanstack-query/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/solid/with-tanstack-query/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/with-tanstack-query/src/index.tsx b/examples/solid/with-tanstack-query/src/index.tsx new file mode 100644 index 0000000000..43087f3ce9 --- /dev/null +++ b/examples/solid/with-tanstack-query/src/index.tsx @@ -0,0 +1,15 @@ +import { render } from 'solid-js/web' +import { QueryClient, QueryClientProvider } from '@tanstack/solid-query' +import './index.css' +import App from './App' + +const queryClient = new QueryClient() + +render( + () => ( + + + + ), + document.getElementById('root') as HTMLElement, +) diff --git a/examples/solid/with-tanstack-query/tsconfig.json b/examples/solid/with-tanstack-query/tsconfig.json new file mode 100644 index 0000000000..cb996fae69 --- /dev/null +++ b/examples/solid/with-tanstack-query/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts", "vite.config.js"] +} diff --git a/examples/solid/with-tanstack-query/vite.config.ts b/examples/solid/with-tanstack-query/vite.config.ts new file mode 100644 index 0000000000..b80a99b402 --- /dev/null +++ b/examples/solid/with-tanstack-query/vite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite' +import solidPlugin from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solidPlugin()], + build: { + target: 'esnext', + }, +}) diff --git a/examples/solid/with-tanstack-router/index.html b/examples/solid/with-tanstack-router/index.html new file mode 100644 index 0000000000..4d3220bdd2 --- /dev/null +++ b/examples/solid/with-tanstack-router/index.html @@ -0,0 +1,13 @@ + + + + + + Solid App + + + +
+ + + diff --git a/examples/solid/with-tanstack-router/package.json b/examples/solid/with-tanstack-router/package.json new file mode 100644 index 0000000000..810d9a74fc --- /dev/null +++ b/examples/solid/with-tanstack-router/package.json @@ -0,0 +1,26 @@ +{ + "name": "tanstack-solid-table-example-with-tanstack-router", + "description": "", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "lint": "eslint ./src" + }, + "license": "MIT", + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/router-vite-plugin": "^1.166.50", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vite-plugin-solid": "^2.11.12" + }, + "dependencies": { + "@tanstack/solid-pacer": "^0.21.0", + "@tanstack/solid-query": "^5.100.9", + "@tanstack/solid-router": "^1.169.2", + "@tanstack/solid-table": "^9.0.0-alpha.45", + "solid-js": "^1.9.12" + } +} diff --git a/examples/solid/with-tanstack-router/src/App.tsx b/examples/solid/with-tanstack-router/src/App.tsx new file mode 100644 index 0000000000..94db2e7429 --- /dev/null +++ b/examples/solid/with-tanstack-router/src/App.tsx @@ -0,0 +1,21 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/solid-query' +import { RouterProvider, createRouter } from '@tanstack/solid-router' +import { routeTree } from './routeTree.gen' + +const router = createRouter({ routeTree }) + +declare module '@tanstack/solid-router' { + interface Register { + router: typeof router + } +} + +const queryClient = new QueryClient() + +export default function App() { + return ( + + + + ) +} diff --git a/examples/solid/with-tanstack-router/src/api/types.ts b/examples/solid/with-tanstack-router/src/api/types.ts new file mode 100644 index 0000000000..f3a337c5b9 --- /dev/null +++ b/examples/solid/with-tanstack-router/src/api/types.ts @@ -0,0 +1,10 @@ +import type { PaginationState } from '@tanstack/solid-table' + +export type PaginatedData = { + result: Array + rowCount: number +} + +export type PaginationParams = PaginationState +export type SortParams = { sortBy: `${string}.${'asc' | 'desc'}` } +export type Filters = Partial diff --git a/examples/solid/with-tanstack-router/src/api/user.ts b/examples/solid/with-tanstack-router/src/api/user.ts new file mode 100644 index 0000000000..2d51c95598 --- /dev/null +++ b/examples/solid/with-tanstack-router/src/api/user.ts @@ -0,0 +1,71 @@ +import { faker } from '@faker-js/faker' +import type { Filters, PaginatedData } from './types' + +const DEFAULT_PAGE = 0 +const DEFAULT_PAGE_SIZE = 10 + +export type User = { + id: number + firstName: string + lastName: string + age: number +} + +export type UserFilters = Filters + +function makeData(amount: number): Array { + return Array(amount) + .fill(0) + .map((_, index) => ({ + id: index + 1, + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + })) +} + +const data = makeData(1000) + +export async function fetchUsers( + filtersAndPagination: UserFilters, +): Promise> { + console.log('fetchUsers', filtersAndPagination) + const { + pageIndex = DEFAULT_PAGE, + pageSize = DEFAULT_PAGE_SIZE, + sortBy, + ...filters + } = filtersAndPagination + const requestedData = data.slice() + + if (sortBy) { + const [field, order] = sortBy.split('.') + requestedData.sort((a, b) => { + const aValue = a[field as keyof User] + const bValue = b[field as keyof User] + if (aValue === bValue) return 0 + if (order === 'asc') return aValue > bValue ? 1 : -1 + return aValue < bValue ? 1 : -1 + }) + } + + const filteredData = requestedData.filter((user) => { + return Object.keys(filters).every((key) => { + const filter = filters[key as keyof User] + if (filter === undefined || filter === '') return true + const value = user[key as keyof User] + if (typeof value === 'number') return value === +filter + return value.toLowerCase().includes(`${filter}`.toLowerCase()) + }) + }) + + await new Promise((resolve) => setTimeout(resolve, 100)) + + return { + result: filteredData.slice( + pageIndex * pageSize, + (pageIndex + 1) * pageSize, + ), + rowCount: filteredData.length, + } +} diff --git a/examples/solid/with-tanstack-router/src/components/debouncedInput.tsx b/examples/solid/with-tanstack-router/src/components/debouncedInput.tsx new file mode 100644 index 0000000000..cf51c46339 --- /dev/null +++ b/examples/solid/with-tanstack-router/src/components/debouncedInput.tsx @@ -0,0 +1,46 @@ +import { createDebouncer } from '@tanstack/solid-pacer/debouncer' +import { createEffect, createSignal } from 'solid-js' + +type DebouncedInputProps = { + value: string | number + onChange: (value: string | number) => void + debounce?: number + class?: string + type?: string + placeholder?: string +} + +export function DebouncedInput(props: DebouncedInputProps) { + const [value, setValue] = createSignal(props.value) + + createEffect(() => { + setValue(() => props.value) + }) + + const onChangeDebouncer = createDebouncer( + (nextValue: string | number) => props.onChange(nextValue), + { wait: () => props.debounce ?? 200 }, + ) + + createEffect(() => { + onChangeDebouncer.maybeExecute(value()) + }) + + return ( + { + if (e.currentTarget.value === '') { + setValue('') + } else if (props.type === 'number') { + setValue(e.currentTarget.valueAsNumber) + } else { + setValue(e.currentTarget.value) + } + }} + placeholder={props.placeholder} + class={props.class} + /> + ) +} diff --git a/examples/solid/with-tanstack-router/src/components/table.tsx b/examples/solid/with-tanstack-router/src/components/table.tsx new file mode 100644 index 0000000000..471d9c545a --- /dev/null +++ b/examples/solid/with-tanstack-router/src/components/table.tsx @@ -0,0 +1,229 @@ +import { + columnFilteringFeature, + createTable, + rowPaginationFeature, + rowSelectionFeature, + rowSortingFeature, + tableFeatures, +} from '@tanstack/solid-table' +import { For, Show, createEffect } from 'solid-js' +import { DebouncedInput } from './debouncedInput' +import type { + ColumnDef, + OnChangeFn, + PaginationState, + SortingState, + TableOptions_RowPagination, +} from '@tanstack/solid-table' +import type { Filters } from '../api/types' + +export const _features = tableFeatures({ + columnFilteringFeature, + rowPaginationFeature, + rowSelectionFeature, + rowSortingFeature, +}) + +export const DEFAULT_PAGE_INDEX = 0 +export const DEFAULT_PAGE_SIZE = 10 + +type Props> = { + data: Array + columns: Array> + pagination: PaginationState + paginationOptions: Pick< + TableOptions_RowPagination, + 'onPaginationChange' | 'rowCount' + > + filters: Filters + onFilterChange: (dataFilters: Partial) => void + sorting: SortingState + onSortingChange: OnChangeFn +} + +export default function Table>( + props: Props, +) { + const table = createTable( + { + debugTable: true, + _features, + _rowModels: {}, + get columns() { + return props.columns + }, + get data() { + return props.data + }, + manualFiltering: true, + manualPagination: true, + manualSorting: true, + get onSortingChange() { + return props.onSortingChange + }, + get onPaginationChange() { + return props.paginationOptions.onPaginationChange + }, + get rowCount() { + return props.paginationOptions.rowCount + }, + }, + (state) => state, + ) + + // Sync controlled state with table store + createEffect(() => { + table.baseStore.setState((prev) => ({ + ...prev, + pagination: props.pagination, + sorting: props.sorting, + })) + }) + + return ( +
+ + + + {(headerGroup) => ( + + + {(header) => { + const fieldMeta = header.column.columnDef.meta + return ( + + ) + }} + + + )} + + + + + {(row) => ( + + + {(cell) => ( + + )} + + + )} + + +
+ + <> +
+ + {( + { + asc: ' 🔼', + desc: ' 🔽', + false: ' 🔃', + } as Record + )[header.column.getIsSorted() as string] ?? null} +
+ + { + props.onFilterChange({ + [fieldMeta!.filterKey as keyof T]: value, + } as Partial) + }} + placeholder="Search..." + type={ + fieldMeta?.filterVariant === 'number' + ? 'number' + : 'text' + } + value={ + (props.filters[fieldMeta!.filterKey!] as + | string + | number) ?? '' + } + /> + + +
+
+ +
+
+ + + + + +
Page
+ + {(table.store.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = e.currentTarget.value + ? Number(e.currentTarget.value) - 1 + : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
+
+ ) +} diff --git a/examples/solid/with-tanstack-router/src/hooks/useFilters.ts b/examples/solid/with-tanstack-router/src/hooks/useFilters.ts new file mode 100644 index 0000000000..a8f8121035 --- /dev/null +++ b/examples/solid/with-tanstack-router/src/hooks/useFilters.ts @@ -0,0 +1,25 @@ +import { getRouteApi } from '@tanstack/solid-router' +import { cleanEmptyParams } from '../utils/cleanEmptyParams' +import type { RegisteredRouter, RouteIds } from '@tanstack/solid-router' + +export function useFilters>( + routeId: T, +) { + const routeApi = getRouteApi(routeId) + const navigate = routeApi.useNavigate() + const filters = routeApi.useSearch() + + const setFilters = (partialFilters: Partial>) => + navigate({ + search: (prev) => cleanEmptyParams({ ...prev, ...partialFilters }), + replace: true, + } as Parameters[0]) + + const resetFilters = () => + navigate({ + search: () => ({}), + replace: true, + } as Parameters[0]) + + return { filters, setFilters, resetFilters } +} diff --git a/examples/solid/with-tanstack-router/src/index.css b/examples/solid/with-tanstack-router/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/solid/with-tanstack-router/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/solid/with-tanstack-router/src/index.tsx b/examples/solid/with-tanstack-router/src/index.tsx new file mode 100644 index 0000000000..ea9c952177 --- /dev/null +++ b/examples/solid/with-tanstack-router/src/index.tsx @@ -0,0 +1,6 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import './index.css' +import App from './App' + +render(() => , document.getElementById('root') as HTMLElement) diff --git a/examples/solid/with-tanstack-router/src/routeTree.gen.ts b/examples/solid/with-tanstack-router/src/routeTree.gen.ts new file mode 100644 index 0000000000..5c85d8704c --- /dev/null +++ b/examples/solid/with-tanstack-router/src/routeTree.gen.ts @@ -0,0 +1,77 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as AnotherRouteRouteImport } from './routes/anotherRoute' +import { Route as IndexRouteImport } from './routes/index' + +const AnotherRouteRoute = AnotherRouteRouteImport.update({ + id: '/anotherRoute', + path: '/anotherRoute', + getParentRoute: () => rootRouteImport, +} as any) +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/anotherRoute': typeof AnotherRouteRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/anotherRoute': typeof AnotherRouteRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/anotherRoute': typeof AnotherRouteRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/anotherRoute' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/anotherRoute' + id: '__root__' | '/' | '/anotherRoute' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + AnotherRouteRoute: typeof AnotherRouteRoute +} + +declare module '@tanstack/solid-router' { + interface FileRoutesByPath { + '/anotherRoute': { + id: '/anotherRoute' + path: '/anotherRoute' + fullPath: '/anotherRoute' + preLoaderRoute: typeof AnotherRouteRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + AnotherRouteRoute: AnotherRouteRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/examples/solid/with-tanstack-router/src/routes/__root.tsx b/examples/solid/with-tanstack-router/src/routes/__root.tsx new file mode 100644 index 0000000000..3ed6e82a7d --- /dev/null +++ b/examples/solid/with-tanstack-router/src/routes/__root.tsx @@ -0,0 +1,5 @@ +import { Outlet, createRootRoute } from '@tanstack/solid-router' + +export const Route = createRootRoute({ + component: Outlet, +}) diff --git a/examples/solid/with-tanstack-router/src/routes/anotherRoute.tsx b/examples/solid/with-tanstack-router/src/routes/anotherRoute.tsx new file mode 100644 index 0000000000..9e29f87556 --- /dev/null +++ b/examples/solid/with-tanstack-router/src/routes/anotherRoute.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/anotherRoute')({ + validateSearch: () => ({}) as { foo: string; bar: number }, +}) diff --git a/examples/solid/with-tanstack-router/src/routes/index.tsx b/examples/solid/with-tanstack-router/src/routes/index.tsx new file mode 100644 index 0000000000..95d3861fe3 --- /dev/null +++ b/examples/solid/with-tanstack-router/src/routes/index.tsx @@ -0,0 +1,80 @@ +import { keepPreviousData, useQuery } from '@tanstack/solid-query' +import { createFileRoute } from '@tanstack/solid-router' +import { fetchUsers } from '../api/user' +import Table, { + DEFAULT_PAGE_INDEX, + DEFAULT_PAGE_SIZE, +} from '../components/table' +import { useFilters } from '../hooks/useFilters' +import { sortByToState, stateToSortBy } from '../utils/tableSortMapper' +import { USER_COLUMNS } from '../utils/userColumns' +import type { UserFilters } from '../api/user' + +export const Route = createFileRoute('/')({ + component: UsersPage, + validateSearch: () => ({}) as UserFilters, +}) + +function UsersPage() { + const { filters, resetFilters, setFilters } = useFilters(Route.fullPath) + + const dataQuery = useQuery(() => ({ + queryKey: ['users', filters()], + queryFn: () => fetchUsers(filters()), + placeholderData: keepPreviousData, + })) + + const paginationState = () => ({ + pageIndex: filters().pageIndex ?? DEFAULT_PAGE_INDEX, + pageSize: filters().pageSize ?? DEFAULT_PAGE_SIZE, + }) + + const sortingState = () => sortByToState(filters().sortBy) + + return ( +
+

TanStack Table + Query + Router

+ { + setFilters( + typeof pagination === 'function' + ? pagination(paginationState()) + : pagination, + ) + }, + rowCount: dataQuery.data?.rowCount, + }} + filters={filters()} + onFilterChange={(newFilters) => + setFilters({ ...newFilters, pageIndex: DEFAULT_PAGE_INDEX }) + } + sorting={sortingState()} + onSortingChange={(updaterOrValue) => { + const newSortingState = + typeof updaterOrValue === 'function' + ? updaterOrValue(sortingState()) + : updaterOrValue + return setFilters({ + sortBy: stateToSortBy(newSortingState), + pageIndex: DEFAULT_PAGE_INDEX, + }) + }} + /> +
+ {dataQuery.data?.rowCount?.toLocaleString()} users found + +
+
{JSON.stringify(filters(), null, 2)}
+ + ) +} diff --git a/examples/solid/with-tanstack-router/src/utils/cleanEmptyParams.ts b/examples/solid/with-tanstack-router/src/utils/cleanEmptyParams.ts new file mode 100644 index 0000000000..e3dc968bb8 --- /dev/null +++ b/examples/solid/with-tanstack-router/src/utils/cleanEmptyParams.ts @@ -0,0 +1,21 @@ +import { DEFAULT_PAGE_INDEX, DEFAULT_PAGE_SIZE } from '../components/table' + +export const cleanEmptyParams = >( + search: T, +): T => { + const newSearch = { ...search } + Object.keys(newSearch).forEach((key) => { + const value = newSearch[key] + if ( + value === undefined || + value === '' || + (typeof value === 'number' && isNaN(value)) + ) + delete newSearch[key] + }) + + if (search.pageIndex === DEFAULT_PAGE_INDEX) delete newSearch.pageIndex + if (search.pageSize === DEFAULT_PAGE_SIZE) delete newSearch.pageSize + + return newSearch +} diff --git a/examples/solid/with-tanstack-router/src/utils/tableSortMapper.ts b/examples/solid/with-tanstack-router/src/utils/tableSortMapper.ts new file mode 100644 index 0000000000..cb74f0f800 --- /dev/null +++ b/examples/solid/with-tanstack-router/src/utils/tableSortMapper.ts @@ -0,0 +1,14 @@ +import type { SortingState } from '@tanstack/solid-table' +import type { SortParams } from '../api/types' + +export const stateToSortBy = (sorting: SortingState | undefined) => { + if (!sorting || sorting.length == 0) return undefined + const sort = sorting[0] + return `${sort.id}.${sort.desc ? 'desc' : 'asc'}` as const +} + +export const sortByToState = (sortBy: SortParams['sortBy'] | undefined) => { + if (!sortBy) return [] + const [id, desc] = sortBy.split('.') + return [{ id, desc: desc === 'desc' }] +} diff --git a/examples/solid/with-tanstack-router/src/utils/userColumns.tsx b/examples/solid/with-tanstack-router/src/utils/userColumns.tsx new file mode 100644 index 0000000000..e2b7614be0 --- /dev/null +++ b/examples/solid/with-tanstack-router/src/utils/userColumns.tsx @@ -0,0 +1,41 @@ +import type { + CellData, + ColumnDef, + RowData, + TableFeatures, +} from '@tanstack/solid-table' +import type { User } from '../api/user' + +declare module '@tanstack/solid-table' { + interface ColumnMeta< + TFeatures extends TableFeatures, + TData extends RowData, + TValue extends CellData = CellData, + > { + filterKey?: keyof TData + filterVariant?: 'text' | 'number' + } +} + +export const USER_COLUMNS: Array> = [ + { + accessorKey: 'id', + header: () => ID, + meta: { filterKey: 'id', filterVariant: 'number' }, + }, + { + accessorKey: 'firstName', + header: () => First Name, + meta: { filterKey: 'firstName' }, + }, + { + accessorKey: 'lastName', + header: () => Last Name, + meta: { filterKey: 'lastName' }, + }, + { + accessorKey: 'age', + header: () => 'Age', + meta: { filterKey: 'age', filterVariant: 'number' }, + }, +] diff --git a/examples/solid/with-tanstack-router/src/vite-env.d.ts b/examples/solid/with-tanstack-router/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/solid/with-tanstack-router/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/solid/with-tanstack-router/tsconfig.json b/examples/solid/with-tanstack-router/tsconfig.json new file mode 100644 index 0000000000..cb996fae69 --- /dev/null +++ b/examples/solid/with-tanstack-router/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts", "vite.config.js"] +} diff --git a/examples/solid/with-tanstack-router/vite.config.ts b/examples/solid/with-tanstack-router/vite.config.ts new file mode 100644 index 0000000000..2735c127ed --- /dev/null +++ b/examples/solid/with-tanstack-router/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite' +import solidPlugin from 'vite-plugin-solid' +import { TanStackRouterVite } from '@tanstack/router-vite-plugin' + +export default defineConfig({ + plugins: [solidPlugin(), TanStackRouterVite({ target: 'solid' })], + build: { + target: 'esnext', + }, +}) diff --git a/examples/svelte/basic/.gitignore b/examples/svelte/basic-app-table/.gitignore similarity index 100% rename from examples/svelte/basic/.gitignore rename to examples/svelte/basic-app-table/.gitignore diff --git a/examples/svelte/basic-app-table/README.md b/examples/svelte/basic-app-table/README.md new file mode 100644 index 0000000000..b168d3c4b1 --- /dev/null +++ b/examples/svelte/basic-app-table/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run start` or `yarn start` diff --git a/examples/svelte/basic-app-table/index.html b/examples/svelte/basic-app-table/index.html new file mode 100644 index 0000000000..8441238e0e --- /dev/null +++ b/examples/svelte/basic-app-table/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/svelte/basic-app-table/package.json b/examples/svelte/basic-app-table/package.json new file mode 100644 index 0000000000..7d27f7561b --- /dev/null +++ b/examples/svelte/basic-app-table/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-svelte-table-example-basic-app-table", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" + }, + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/svelte/basic-app-table/src/App.svelte b/examples/svelte/basic-app-table/src/App.svelte new file mode 100644 index 0000000000..fd1a656f6a --- /dev/null +++ b/examples/svelte/basic-app-table/src/App.svelte @@ -0,0 +1,146 @@ + + + +
+
+ + +
+
+ + {#each table.getHeaderGroups() as headerGroup (headerGroup.id) + } + + {#each headerGroup.headers as header (header.id)} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getAllCells() as cell (cell.id)} + + {/each} + + {/each} + + + {#each table.getFooterGroups() as footerGroup (footerGroup.id)} + + {#each footerGroup.headers as header (header.id)} + + {/each} + + {/each} + +
+ {#if !header.isPlaceholder} +
{ + if (e.key === 'Enter' || e.key === ' ') { + header.column.getToggleSortingHandler()?.(e) + } + }} + > + + {#if header.column.getIsSorted() === 'asc'} + {' '}🔼 + {:else if header.column.getIsSorted() === 'desc'} + {' '}🔽 + {/if} +
+ {/if} +
+ +
+ {#if !header.isPlaceholder} + + {/if} +
+
+
{JSON.stringify(table.store.state, null, 2)
+  }
+
diff --git a/examples/svelte/basic-app-table/src/index.css b/examples/svelte/basic-app-table/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/svelte/basic-app-table/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/basic-app-table/src/main.ts b/examples/svelte/basic-app-table/src/main.ts new file mode 100644 index 0000000000..b89f957c19 --- /dev/null +++ b/examples/svelte/basic-app-table/src/main.ts @@ -0,0 +1,9 @@ +// @ts-ignore - Svelte mount types not properly recognized +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('root')!, +}) + +export default app diff --git a/examples/svelte/basic-app-table/src/makeData.ts b/examples/svelte/basic-app-table/src/makeData.ts new file mode 100644 index 0000000000..b9fb014aba --- /dev/null +++ b/examples/svelte/basic-app-table/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/svelte/basic/svelte.config.js b/examples/svelte/basic-app-table/svelte.config.js similarity index 100% rename from examples/svelte/basic/svelte.config.js rename to examples/svelte/basic-app-table/svelte.config.js diff --git a/examples/svelte/basic-app-table/tsconfig.json b/examples/svelte/basic-app-table/tsconfig.json new file mode 100644 index 0000000000..6761f6aaa4 --- /dev/null +++ b/examples/svelte/basic-app-table/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/svelte/basic/vite.config.js b/examples/svelte/basic-app-table/vite.config.js similarity index 100% rename from examples/svelte/basic/vite.config.js rename to examples/svelte/basic-app-table/vite.config.js diff --git a/examples/svelte/basic-create-table/.gitignore b/examples/svelte/basic-create-table/.gitignore new file mode 100644 index 0000000000..91c18232e2 --- /dev/null +++ b/examples/svelte/basic-create-table/.gitignore @@ -0,0 +1,8 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local + +src/**/*.d.ts +src/**/*.map \ No newline at end of file diff --git a/examples/svelte/basic-create-table/README.md b/examples/svelte/basic-create-table/README.md new file mode 100644 index 0000000000..b168d3c4b1 --- /dev/null +++ b/examples/svelte/basic-create-table/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run start` or `yarn start` diff --git a/examples/svelte/basic-create-table/index.html b/examples/svelte/basic-create-table/index.html new file mode 100644 index 0000000000..8441238e0e --- /dev/null +++ b/examples/svelte/basic-create-table/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/svelte/basic-create-table/package.json b/examples/svelte/basic-create-table/package.json new file mode 100644 index 0000000000..efbd5919b2 --- /dev/null +++ b/examples/svelte/basic-create-table/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-svelte-table-example-basic-create-table", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" + }, + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/svelte/basic-create-table/src/App.svelte b/examples/svelte/basic-create-table/src/App.svelte new file mode 100644 index 0000000000..f84447f2f7 --- /dev/null +++ b/examples/svelte/basic-create-table/src/App.svelte @@ -0,0 +1,115 @@ + + + +
+
+ + +
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id) + } + + {#each headerGroup.headers as header (header.id)} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getAllCells() as cell (cell.id)} + + {/each} + + {/each} + + + {#each table.getFooterGroups() as footerGroup (footerGroup.id)} + + {#each footerGroup.headers as header (header.id)} + + {/each} + + {/each} + +
+ {#if !header.isPlaceholder} + + {/if} +
+ +
+ {#if !header.isPlaceholder} + + {/if} +
+
+
diff --git a/examples/svelte/basic-create-table/src/index.css b/examples/svelte/basic-create-table/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/svelte/basic-create-table/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/basic-create-table/src/main.ts b/examples/svelte/basic-create-table/src/main.ts new file mode 100644 index 0000000000..316b949e6a --- /dev/null +++ b/examples/svelte/basic-create-table/src/main.ts @@ -0,0 +1,9 @@ +// @ts-ignore - Svelte type definitions are not properly recognized +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('root')!, +}) + +export default app diff --git a/examples/svelte/basic-create-table/src/makeData.ts b/examples/svelte/basic-create-table/src/makeData.ts new file mode 100644 index 0000000000..b9fb014aba --- /dev/null +++ b/examples/svelte/basic-create-table/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/svelte/basic-create-table/svelte.config.js b/examples/svelte/basic-create-table/svelte.config.js new file mode 100644 index 0000000000..8abe4369b8 --- /dev/null +++ b/examples/svelte/basic-create-table/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/basic-create-table/tsconfig.json b/examples/svelte/basic-create-table/tsconfig.json new file mode 100644 index 0000000000..6761f6aaa4 --- /dev/null +++ b/examples/svelte/basic-create-table/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/svelte/basic-create-table/vite.config.js b/examples/svelte/basic-create-table/vite.config.js new file mode 100644 index 0000000000..c6ced40a24 --- /dev/null +++ b/examples/svelte/basic-create-table/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + svelte(), + ], +}) diff --git a/examples/svelte/basic-external-atoms/index.html b/examples/svelte/basic-external-atoms/index.html new file mode 100644 index 0000000000..8441238e0e --- /dev/null +++ b/examples/svelte/basic-external-atoms/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/svelte/basic-external-atoms/package.json b/examples/svelte/basic-external-atoms/package.json new file mode 100644 index 0000000000..86ab153873 --- /dev/null +++ b/examples/svelte/basic-external-atoms/package.json @@ -0,0 +1,26 @@ +{ + "name": "tanstack-svelte-table-example-basic-external-atoms", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" + }, + "dependencies": { + "@tanstack/svelte-store": "^0.12.0" + }, + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/svelte/basic-external-atoms/src/App.svelte b/examples/svelte/basic-external-atoms/src/App.svelte new file mode 100644 index 0000000000..89872d1ce1 --- /dev/null +++ b/examples/svelte/basic-external-atoms/src/App.svelte @@ -0,0 +1,205 @@ + + +
+
+ + +
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id) + } + + {#each headerGroup.headers as header (header.id)} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getAllCells() as cell (cell.id)} + + {/each} + + {/each} + +
+ {#if !header.isPlaceholder} +
{ + if (e.key === 'Enter' || e.key === ' ') { + header.column.getToggleSortingHandler()?.(e) + } + }} + > + + {#if header.column.getIsSorted() === 'asc'} + {' '}🔼 + {:else if header.column.getIsSorted() === 'desc'} + {' '}🔽 + {/if} +
+ {/if} +
+ +
+
+
+ + + + + +
Page
+ + {(pagination.current.pageIndex + 1).toLocaleString()} of {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = (e.target as HTMLInputElement).value + ? Number((e.target as HTMLInputElement).value) - 1 + : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
+
+
{JSON.stringify({ sorting: sorting.current, pagination: pagination.current }, null, 2)}
+
diff --git a/examples/svelte/basic-external-atoms/src/index.css b/examples/svelte/basic-external-atoms/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/svelte/basic-external-atoms/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/basic-external-atoms/src/main.ts b/examples/svelte/basic-external-atoms/src/main.ts new file mode 100644 index 0000000000..aac57e409c --- /dev/null +++ b/examples/svelte/basic-external-atoms/src/main.ts @@ -0,0 +1,9 @@ +// @ts-ignore -- svelte module types +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('root')!, +}) + +export default app diff --git a/examples/svelte/basic-external-atoms/src/makeData.ts b/examples/svelte/basic-external-atoms/src/makeData.ts new file mode 100644 index 0000000000..6311127267 --- /dev/null +++ b/examples/svelte/basic-external-atoms/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/svelte/basic-external-atoms/src/vite-env.d.ts b/examples/svelte/basic-external-atoms/src/vite-env.d.ts new file mode 100644 index 0000000000..66addc6b05 --- /dev/null +++ b/examples/svelte/basic-external-atoms/src/vite-env.d.ts @@ -0,0 +1,4 @@ +/// +/// + +declare module '*.css' {} diff --git a/examples/svelte/basic-external-atoms/svelte.config.js b/examples/svelte/basic-external-atoms/svelte.config.js new file mode 100644 index 0000000000..8abe4369b8 --- /dev/null +++ b/examples/svelte/basic-external-atoms/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/basic-external-atoms/tsconfig.json b/examples/svelte/basic-external-atoms/tsconfig.json new file mode 100644 index 0000000000..6761f6aaa4 --- /dev/null +++ b/examples/svelte/basic-external-atoms/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/svelte/basic-external-atoms/vite.config.js b/examples/svelte/basic-external-atoms/vite.config.js new file mode 100644 index 0000000000..c6ced40a24 --- /dev/null +++ b/examples/svelte/basic-external-atoms/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + svelte(), + ], +}) diff --git a/examples/svelte/basic-external-state/index.html b/examples/svelte/basic-external-state/index.html new file mode 100644 index 0000000000..8441238e0e --- /dev/null +++ b/examples/svelte/basic-external-state/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/svelte/basic-external-state/package.json b/examples/svelte/basic-external-state/package.json new file mode 100644 index 0000000000..fd07a746b1 --- /dev/null +++ b/examples/svelte/basic-external-state/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-svelte-table-example-basic-external-state", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" + }, + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/svelte/basic-external-state/src/App.svelte b/examples/svelte/basic-external-state/src/App.svelte new file mode 100644 index 0000000000..22f6a4bf40 --- /dev/null +++ b/examples/svelte/basic-external-state/src/App.svelte @@ -0,0 +1,209 @@ + + +
+
+ + +
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id) + } + + {#each headerGroup.headers as header (header.id)} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getAllCells() as cell (cell.id)} + + {/each} + + {/each} + +
+ {#if !header.isPlaceholder} +
{ + if (e.key === 'Enter' || e.key === ' ') { + header.column.getToggleSortingHandler()?.(e) + } + }} + > + + {#if header.column.getIsSorted() === 'asc'} + {' '}🔼 + {:else if header.column.getIsSorted() === 'desc'} + {' '}🔽 + {/if} +
+ {/if} +
+ +
+
+
+ + + + + +
Page
+ + {(pagination().pageIndex + 1).toLocaleString()} of {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = e.currentTarget.value + ? Number(e.currentTarget.value) - 1 + : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
+
+
+    {JSON.stringify(
+      { sorting: sorting(), pagination: pagination() },
+      null,
+      2,
+    )}
+  
+
diff --git a/examples/svelte/basic-external-state/src/index.css b/examples/svelte/basic-external-state/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/svelte/basic-external-state/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/basic-external-state/src/main.ts b/examples/svelte/basic-external-state/src/main.ts new file mode 100644 index 0000000000..aac57e409c --- /dev/null +++ b/examples/svelte/basic-external-state/src/main.ts @@ -0,0 +1,9 @@ +// @ts-ignore -- svelte module types +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('root')!, +}) + +export default app diff --git a/examples/svelte/basic-external-state/src/makeData.ts b/examples/svelte/basic-external-state/src/makeData.ts new file mode 100644 index 0000000000..6311127267 --- /dev/null +++ b/examples/svelte/basic-external-state/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/svelte/basic-external-state/src/vite-env.d.ts b/examples/svelte/basic-external-state/src/vite-env.d.ts new file mode 100644 index 0000000000..66addc6b05 --- /dev/null +++ b/examples/svelte/basic-external-state/src/vite-env.d.ts @@ -0,0 +1,4 @@ +/// +/// + +declare module '*.css' {} diff --git a/examples/svelte/basic-external-state/svelte.config.js b/examples/svelte/basic-external-state/svelte.config.js new file mode 100644 index 0000000000..8abe4369b8 --- /dev/null +++ b/examples/svelte/basic-external-state/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/basic-external-state/tsconfig.json b/examples/svelte/basic-external-state/tsconfig.json new file mode 100644 index 0000000000..6761f6aaa4 --- /dev/null +++ b/examples/svelte/basic-external-state/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/svelte/basic-external-state/vite.config.js b/examples/svelte/basic-external-state/vite.config.js new file mode 100644 index 0000000000..c6ced40a24 --- /dev/null +++ b/examples/svelte/basic-external-state/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + svelte(), + ], +}) diff --git a/examples/svelte/basic-snippets/.gitignore b/examples/svelte/basic-snippets/.gitignore new file mode 100644 index 0000000000..91c18232e2 --- /dev/null +++ b/examples/svelte/basic-snippets/.gitignore @@ -0,0 +1,8 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local + +src/**/*.d.ts +src/**/*.map \ No newline at end of file diff --git a/examples/svelte/basic-snippets/README.md b/examples/svelte/basic-snippets/README.md new file mode 100644 index 0000000000..b168d3c4b1 --- /dev/null +++ b/examples/svelte/basic-snippets/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run start` or `yarn start` diff --git a/examples/svelte/basic-snippets/index.html b/examples/svelte/basic-snippets/index.html new file mode 100644 index 0000000000..8441238e0e --- /dev/null +++ b/examples/svelte/basic-snippets/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/svelte/basic-snippets/package.json b/examples/svelte/basic-snippets/package.json new file mode 100644 index 0000000000..112b5b75a9 --- /dev/null +++ b/examples/svelte/basic-snippets/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-svelte-table-example-basic-snippets", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" + }, + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/svelte/basic-snippets/src/App.svelte b/examples/svelte/basic-snippets/src/App.svelte new file mode 100644 index 0000000000..acebe8bc95 --- /dev/null +++ b/examples/svelte/basic-snippets/src/App.svelte @@ -0,0 +1,121 @@ + + + +
+
+ + +
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id) + } + + {#each headerGroup.headers as header (header.id)} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getAllCells() as cell (cell.id)} + + {/each} + + {/each} + +
+ {#if !header.isPlaceholder} + + {/if} +
+ +
+
diff --git a/examples/svelte/basic-snippets/src/index.css b/examples/svelte/basic-snippets/src/index.css new file mode 100644 index 0000000000..df13745f17 --- /dev/null +++ b/examples/svelte/basic-snippets/src/index.css @@ -0,0 +1,358 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/basic-snippets/src/main.ts b/examples/svelte/basic-snippets/src/main.ts new file mode 100644 index 0000000000..316b949e6a --- /dev/null +++ b/examples/svelte/basic-snippets/src/main.ts @@ -0,0 +1,9 @@ +// @ts-ignore - Svelte type definitions are not properly recognized +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('root')!, +}) + +export default app diff --git a/examples/svelte/basic-snippets/src/makeData.ts b/examples/svelte/basic-snippets/src/makeData.ts new file mode 100644 index 0000000000..b9fb014aba --- /dev/null +++ b/examples/svelte/basic-snippets/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/svelte/basic-snippets/src/snippets.svelte b/examples/svelte/basic-snippets/src/snippets.svelte new file mode 100644 index 0000000000..37d6012853 --- /dev/null +++ b/examples/svelte/basic-snippets/src/snippets.svelte @@ -0,0 +1,53 @@ + + +{#snippet capitalized(value: string)} +

{value}

+{/snippet} + +{#snippet spectrum({ value, min, max }: SpectrumParams)} +
+ {value} +
+{/snippet} diff --git a/examples/svelte/basic-snippets/svelte.config.js b/examples/svelte/basic-snippets/svelte.config.js new file mode 100644 index 0000000000..8abe4369b8 --- /dev/null +++ b/examples/svelte/basic-snippets/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/basic-snippets/tsconfig.json b/examples/svelte/basic-snippets/tsconfig.json new file mode 100644 index 0000000000..ffcbd5aff9 --- /dev/null +++ b/examples/svelte/basic-snippets/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": [ + "../basic-snippets/src/**/*.ts", + "../basic-snippets/src/**/*.js", + "../basic-snippets/src/**/*.d.ts", + "../basic-snippets/src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/svelte/basic-snippets/vite.config.js b/examples/svelte/basic-snippets/vite.config.js new file mode 100644 index 0000000000..c6ced40a24 --- /dev/null +++ b/examples/svelte/basic-snippets/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + svelte(), + ], +}) diff --git a/examples/svelte/basic/index.html b/examples/svelte/basic/index.html deleted file mode 100644 index 6ab1dd7e51..0000000000 --- a/examples/svelte/basic/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - Vite App - - - - -
- - - diff --git a/examples/svelte/basic/package.json b/examples/svelte/basic/package.json deleted file mode 100644 index 4db5ed4221..0000000000 --- a/examples/svelte/basic/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "tanstack-table-example-svelte-basic", - "version": "0.0.0", - "private": true, - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "serve": "vite preview", - "test:types": "svelte-check --tsconfig ./tsconfig.json" - }, - "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@sveltejs/vite-plugin-svelte": "^3.1.1", - "@tanstack/svelte-table": "^8.20.5", - "@tsconfig/svelte": "^5.0.4", - "svelte": "^4.2.18", - "svelte-check": "^3.8.4", - "typescript": "5.4.5", - "vite": "^5.3.2" - } -} diff --git a/examples/svelte/basic/src/App.svelte b/examples/svelte/basic/src/App.svelte deleted file mode 100644 index a918729a1a..0000000000 --- a/examples/svelte/basic/src/App.svelte +++ /dev/null @@ -1,152 +0,0 @@ - - -
- - - {#each $table.getHeaderGroups() as headerGroup} - - {#each headerGroup.headers as header} - - {/each} - - {/each} - - - {#each $table.getRowModel().rows as row} - - {#each row.getVisibleCells() as cell} - - {/each} - - {/each} - - - {#each $table.getFooterGroups() as footerGroup} - - {#each footerGroup.headers as header} - - {/each} - - {/each} - -
- {#if !header.isPlaceholder} - - {/if} -
- -
- {#if !header.isPlaceholder} - - {/if} -
-
- -
diff --git a/examples/svelte/basic/src/index.css b/examples/svelte/basic/src/index.css deleted file mode 100644 index 43c09e0f6b..0000000000 --- a/examples/svelte/basic/src/index.css +++ /dev/null @@ -1,26 +0,0 @@ -html { - font-family: sans-serif; - font-size: 14px; -} - -table { - border: 1px solid lightgray; -} - -tbody { - border-bottom: 1px solid lightgray; -} - -th { - border-bottom: 1px solid lightgray; - border-right: 1px solid lightgray; - padding: 2px 4px; -} - -tfoot { - color: gray; -} - -tfoot th { - font-weight: normal; -} diff --git a/examples/svelte/basic/src/main.ts b/examples/svelte/basic/src/main.ts deleted file mode 100644 index 4a5606b9a2..0000000000 --- a/examples/svelte/basic/src/main.ts +++ /dev/null @@ -1,8 +0,0 @@ -// @ts-ignore -import App from './App.svelte' - -const app = new App({ - target: document.getElementById('root')!, -}) - -export default app diff --git a/examples/svelte/basic/src/vite-env.d.ts b/examples/svelte/basic/src/vite-env.d.ts deleted file mode 100644 index 4078e7476a..0000000000 --- a/examples/svelte/basic/src/vite-env.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -/// -/// diff --git a/examples/svelte/basic/tsconfig.json b/examples/svelte/basic/tsconfig.json deleted file mode 100644 index e44d928411..0000000000 --- a/examples/svelte/basic/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "@tsconfig/svelte/tsconfig.json", - "compilerOptions": { - "target": "esnext", - "useDefineForClassFields": true, - "module": "esnext", - "resolveJsonModule": true, - "allowJs": true, - "checkJs": true, - "isolatedModules": true - }, - "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"] -} diff --git a/examples/svelte/column-groups/index.html b/examples/svelte/column-groups/index.html index 6ab1dd7e51..8441238e0e 100644 --- a/examples/svelte/column-groups/index.html +++ b/examples/svelte/column-groups/index.html @@ -4,7 +4,6 @@ Vite App - diff --git a/examples/svelte/column-groups/package.json b/examples/svelte/column-groups/package.json index 302eba7f48..2e48d91323 100644 --- a/examples/svelte/column-groups/package.json +++ b/examples/svelte/column-groups/package.json @@ -1,22 +1,23 @@ { - "name": "tanstack-table-example-svelte-column-groups", - "version": "0.0.0", + "name": "tanstack-svelte-table-example-column-groups", "private": true, "type": "module", "scripts": { "dev": "vite", "build": "vite build", "serve": "vite preview", - "test:types": "svelte-check --tsconfig ./tsconfig.json" + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" }, "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@sveltejs/vite-plugin-svelte": "^3.1.1", - "@tanstack/svelte-table": "^8.20.5", - "@tsconfig/svelte": "^5.0.4", - "svelte": "^4.2.18", - "svelte-check": "^3.8.4", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/svelte/column-groups/src/App.svelte b/examples/svelte/column-groups/src/App.svelte index ac780c0da3..4083df5313 100644 --- a/examples/svelte/column-groups/src/App.svelte +++ b/examples/svelte/column-groups/src/App.svelte @@ -1,76 +1,46 @@ -
+
+
+ + +
- {#each $table.getHeaderGroups() as headerGroup} + {#each table.getHeaderGroups() as headerGroup (headerGroup.id) + } - {#each headerGroup.headers as header} + {#each headerGroup.headers as header (header.id)} {/each} @@ -133,30 +98,23 @@ {/each} - {#each $table.getRowModel().rows as row} + {#each table.getRowModel().rows as row (row.id)} - {#each row.getVisibleCells() as cell} + {#each row.getAllCells() as cell (cell.id)} {/each} {/each} - {#each $table.getFooterGroups() as footerGroup} + {#each table.getFooterGroups() as footerGroup (footerGroup.id)} - {#each footerGroup.headers as header} + {#each footerGroup.headers as header (header.id)} {/each} @@ -164,6 +122,5 @@ {/each}
{#if !header.isPlaceholder} - + {/if}
- +
{#if !header.isPlaceholder} - + {/if}
-
- +
diff --git a/examples/svelte/column-groups/src/index.css b/examples/svelte/column-groups/src/index.css index 43c09e0f6b..b9af6ded32 100644 --- a/examples/svelte/column-groups/src/index.css +++ b/examples/svelte/column-groups/src/index.css @@ -5,6 +5,8 @@ html { table { border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; } tbody { @@ -24,3 +26,340 @@ tfoot { tfoot th { font-weight: normal; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/column-groups/src/main.ts b/examples/svelte/column-groups/src/main.ts index 4a5606b9a2..aac57e409c 100644 --- a/examples/svelte/column-groups/src/main.ts +++ b/examples/svelte/column-groups/src/main.ts @@ -1,7 +1,8 @@ -// @ts-ignore +// @ts-ignore -- svelte module types +import { mount } from 'svelte' import App from './App.svelte' -const app = new App({ +const app = mount(App, { target: document.getElementById('root')!, }) diff --git a/examples/svelte/column-groups/src/makeData.ts b/examples/svelte/column-groups/src/makeData.ts new file mode 100644 index 0000000000..b9fb014aba --- /dev/null +++ b/examples/svelte/column-groups/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/svelte/column-groups/tsconfig.json b/examples/svelte/column-groups/tsconfig.json index e44d928411..6761f6aaa4 100644 --- a/examples/svelte/column-groups/tsconfig.json +++ b/examples/svelte/column-groups/tsconfig.json @@ -9,5 +9,13 @@ "checkJs": true, "isolatedModules": true }, - "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"] + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] } diff --git a/examples/svelte/column-ordering/index.html b/examples/svelte/column-ordering/index.html index 6ab1dd7e51..8441238e0e 100644 --- a/examples/svelte/column-ordering/index.html +++ b/examples/svelte/column-ordering/index.html @@ -4,7 +4,6 @@ Vite App - diff --git a/examples/svelte/column-ordering/package.json b/examples/svelte/column-ordering/package.json index ab13648ec7..73c2459ef2 100644 --- a/examples/svelte/column-ordering/package.json +++ b/examples/svelte/column-ordering/package.json @@ -1,23 +1,23 @@ { - "name": "tanstack-table-example-svelte-column-ordering", - "version": "0.0.0", + "name": "tanstack-svelte-table-example-column-ordering", "private": true, "type": "module", "scripts": { "dev": "vite", "build": "vite build", "serve": "vite preview", - "test:types": "svelte-check --tsconfig ./tsconfig.json" + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" }, "devDependencies": { - "@faker-js/faker": "^8.4.1", - "@rollup/plugin-replace": "^5.0.7", - "@sveltejs/vite-plugin-svelte": "^3.1.1", - "@tanstack/svelte-table": "^8.20.5", - "@tsconfig/svelte": "^5.0.4", - "svelte": "^4.2.18", - "svelte-check": "^3.8.4", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/svelte/column-ordering/src/App.svelte b/examples/svelte/column-ordering/src/App.svelte index d23b8c84a0..f1048ecd87 100644 --- a/examples/svelte/column-ordering/src/App.svelte +++ b/examples/svelte/column-ordering/src/App.svelte @@ -1,49 +1,53 @@ -
-
-
+
+
+ + +
+
+
- {#each $table.getAllLeafColumns() as column} -
+ {#each table.getAllLeafColumns() as column} +
{/each}
-
-
- + -
-
- +
+
- {#each $table.getHeaderGroups() as headerGroup} + {#each table.getHeaderGroups() as headerGroup (headerGroup.id) + } - {#each headerGroup.headers as header} + {#each headerGroup.headers as header (header.id)} {/each} @@ -191,18 +168,17 @@ {/each} - {#each $table.getCoreRowModel().rows.slice(0, 20) as row} + {#each table.getCoreRowModel().rows.slice(0, 20) as row (row.id)} - {#each row.getVisibleCells() as cell} + {#each row.getVisibleCells() as cell (cell.id)} {/each} {/each}
{#if !header.isPlaceholder} - + {/if}
- +
-
{JSON.stringify($table.getState().columnOrder, null, 2)}
+
{JSON.stringify(table.store.state.columnOrder, null, 2)
+  }
diff --git a/examples/svelte/column-ordering/src/index.css b/examples/svelte/column-ordering/src/index.css index 93034cdd1b..efd0b26417 100644 --- a/examples/svelte/column-ordering/src/index.css +++ b/examples/svelte/column-ordering/src/index.css @@ -5,6 +5,8 @@ html { table { border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; } tbody { @@ -33,3 +35,340 @@ tfoot { tfoot th { font-weight: normal; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/column-ordering/src/main.ts b/examples/svelte/column-ordering/src/main.ts index 4a5606b9a2..aac57e409c 100644 --- a/examples/svelte/column-ordering/src/main.ts +++ b/examples/svelte/column-ordering/src/main.ts @@ -1,7 +1,8 @@ -// @ts-ignore +// @ts-ignore -- svelte module types +import { mount } from 'svelte' import App from './App.svelte' -const app = new App({ +const app = mount(App, { target: document.getElementById('root')!, }) diff --git a/examples/svelte/column-ordering/src/makeData.ts b/examples/svelte/column-ordering/src/makeData.ts index 331dd1eb19..b9fb014aba 100644 --- a/examples/svelte/column-ordering/src/makeData.ts +++ b/examples/svelte/column-ordering/src/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,13 +29,13 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] return range(len).map((d): Person => { return { ...newPerson(), diff --git a/examples/svelte/column-ordering/tsconfig.json b/examples/svelte/column-ordering/tsconfig.json index e44d928411..6761f6aaa4 100644 --- a/examples/svelte/column-ordering/tsconfig.json +++ b/examples/svelte/column-ordering/tsconfig.json @@ -9,5 +9,13 @@ "checkJs": true, "isolatedModules": true }, - "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"] + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] } diff --git a/examples/svelte/column-pinning-split/.gitignore b/examples/svelte/column-pinning-split/.gitignore new file mode 100644 index 0000000000..2de1736a62 --- /dev/null +++ b/examples/svelte/column-pinning-split/.gitignore @@ -0,0 +1,8 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local + +src/**/*.d.ts +src/**/*.map diff --git a/examples/svelte/column-pinning-split/index.html b/examples/svelte/column-pinning-split/index.html new file mode 100644 index 0000000000..8441238e0e --- /dev/null +++ b/examples/svelte/column-pinning-split/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/svelte/column-pinning-split/package.json b/examples/svelte/column-pinning-split/package.json new file mode 100644 index 0000000000..4ba12e9f99 --- /dev/null +++ b/examples/svelte/column-pinning-split/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-svelte-table-example-column-pinning-split", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" + }, + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/svelte/column-pinning-split/src/App.svelte b/examples/svelte/column-pinning-split/src/App.svelte new file mode 100644 index 0000000000..6f98c4f721 --- /dev/null +++ b/examples/svelte/column-pinning-split/src/App.svelte @@ -0,0 +1,323 @@ + + +
+
+ + +
+
+
+ +
+ {#each table.getAllLeafColumns() as column} +
+ +
+ {/each} +
+
+
+ + + +
+
+

+ This example takes advantage of the "splitting" APIs. (APIs that have + "left", "center", and "right" modifiers) +

+
+ + + {#each table.getLeftHeaderGroups() as headerGroup (headerGroup.id) + } + + {#each headerGroup.headers as header (header.id)} + + {/each} + + {/each} + + + {#each table.getRowModel().rows.slice(0, 20) as row (row.id)} + + {#each row.getLeftVisibleCells() as cell (cell.id)} + + {/each} + + {/each} + +
+
+ {#if !header.isPlaceholder} + + {/if} +
+ {#if !header.isPlaceholder && header.column.getCanPin()} +
+ {#if header.column.getIsPinned() !== 'left'} + + {/if} + {#if header.column.getIsPinned()} + + {/if} + {#if header.column.getIsPinned() !== 'right'} + + {/if} +
+ {/if} +
+ +
+ + + {#each table.getCenterHeaderGroups() as headerGroup (headerGroup.id) + } + + {#each headerGroup.headers as header (header.id)} + + {/each} + + {/each} + + + {#each table.getRowModel().rows.slice(0, 20) as row (row.id)} + + {#each row.getCenterVisibleCells() as cell (cell.id)} + + {/each} + + {/each} + +
+
+ {#if !header.isPlaceholder} + + {/if} +
+ {#if !header.isPlaceholder && header.column.getCanPin()} +
+ {#if header.column.getIsPinned() !== 'left'} + + {/if} + {#if header.column.getIsPinned()} + + {/if} + {#if header.column.getIsPinned() !== 'right'} + + {/if} +
+ {/if} +
+ +
+ + + {#each table.getRightHeaderGroups() as headerGroup (headerGroup.id) + } + + {#each headerGroup.headers as header (header.id)} + + {/each} + + {/each} + + + {#each table.getRowModel().rows.slice(0, 20) as row (row.id)} + + {#each row.getRightVisibleCells() as cell (cell.id)} + + {/each} + + {/each} + +
+
+ {#if !header.isPlaceholder} + + {/if} +
+ {#if !header.isPlaceholder && header.column.getCanPin()} +
+ {#if header.column.getIsPinned() !== 'left'} + + {/if} + {#if header.column.getIsPinned()} + + {/if} + {#if header.column.getIsPinned() !== 'right'} + + {/if} +
+ {/if} +
+ +
+
+
{JSON.stringify(table.state, null, 2)
+  }
+
diff --git a/examples/svelte/column-pinning-split/src/index.css b/examples/svelte/column-pinning-split/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/svelte/column-pinning-split/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/column-pinning-split/src/main.ts b/examples/svelte/column-pinning-split/src/main.ts new file mode 100644 index 0000000000..aac57e409c --- /dev/null +++ b/examples/svelte/column-pinning-split/src/main.ts @@ -0,0 +1,9 @@ +// @ts-ignore -- svelte module types +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('root')!, +}) + +export default app diff --git a/examples/svelte/column-pinning-split/src/makeData.ts b/examples/svelte/column-pinning-split/src/makeData.ts new file mode 100644 index 0000000000..d274a17f45 --- /dev/null +++ b/examples/svelte/column-pinning-split/src/makeData.ts @@ -0,0 +1,47 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + return makeDataLevel() +} diff --git a/examples/svelte/column-pinning-split/svelte.config.js b/examples/svelte/column-pinning-split/svelte.config.js new file mode 100644 index 0000000000..8abe4369b8 --- /dev/null +++ b/examples/svelte/column-pinning-split/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/column-pinning-split/tsconfig.json b/examples/svelte/column-pinning-split/tsconfig.json new file mode 100644 index 0000000000..6761f6aaa4 --- /dev/null +++ b/examples/svelte/column-pinning-split/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/svelte/column-pinning-split/vite.config.js b/examples/svelte/column-pinning-split/vite.config.js new file mode 100644 index 0000000000..c6ced40a24 --- /dev/null +++ b/examples/svelte/column-pinning-split/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + svelte(), + ], +}) diff --git a/examples/svelte/column-pinning-sticky/.gitignore b/examples/svelte/column-pinning-sticky/.gitignore new file mode 100644 index 0000000000..2de1736a62 --- /dev/null +++ b/examples/svelte/column-pinning-sticky/.gitignore @@ -0,0 +1,8 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local + +src/**/*.d.ts +src/**/*.map diff --git a/examples/svelte/column-pinning-sticky/index.html b/examples/svelte/column-pinning-sticky/index.html new file mode 100644 index 0000000000..8441238e0e --- /dev/null +++ b/examples/svelte/column-pinning-sticky/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/svelte/column-pinning-sticky/package.json b/examples/svelte/column-pinning-sticky/package.json new file mode 100644 index 0000000000..27d395f6f2 --- /dev/null +++ b/examples/svelte/column-pinning-sticky/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-svelte-table-example-column-pinning-sticky", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" + }, + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/svelte/column-pinning-sticky/src/App.svelte b/examples/svelte/column-pinning-sticky/src/App.svelte new file mode 100644 index 0000000000..229d751795 --- /dev/null +++ b/examples/svelte/column-pinning-sticky/src/App.svelte @@ -0,0 +1,253 @@ + + +
+
+ + +
+
+
+ +
+ {#each table.getAllLeafColumns() as column} +
+ +
+ {/each} +
+
+
+ + + +
+
+
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} + + {#each headerGroup.headers as header (header.id)} + {@const styles = getCommonPinningStyles(header.column)} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getVisibleCells() as cell (cell.id)} + {@const styles = getCommonPinningStyles(cell.column)} + + {/each} + + {/each} + +
+
+ {#if !header.isPlaceholder} + + {' '} + {/if} + {header.column.getIndex(header.column.getIsPinned() || 'center')} +
+ {#if !header.isPlaceholder && header.column.getCanPin()} +
+ {#if header.column.getIsPinned() !== 'left'} + + {/if} + {#if header.column.getIsPinned()} + + {/if} + {#if header.column.getIsPinned() !== 'right'} + + {/if} +
+ {/if} + +
header.column.resetSize()} + onmousedown={header.getResizeHandler()} + ontouchstart={header.getResizeHandler()} + class="resizer {header.column.getIsResizing() ? 'isResizing' : ''}" + >
+
+ +
+
+
{JSON.stringify(table.state, null, 2)
+  }
+
diff --git a/examples/svelte/column-pinning-sticky/src/index.css b/examples/svelte/column-pinning-sticky/src/index.css new file mode 100644 index 0000000000..cf254482e6 --- /dev/null +++ b/examples/svelte/column-pinning-sticky/src/index.css @@ -0,0 +1,388 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +.table-container { + border: 1px solid lightgray; + overflow-x: scroll; + width: 100%; + max-width: 960px; + position: relative; + border-collapse: collapse; + border-spacing: 0; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +th { + background-color: lightgray; + border-bottom: 1px solid lightgray; + font-weight: bold; + height: 30px; + padding: 2px 4px; + position: relative; + text-align: center; +} + +td { + background-color: white; + padding: 2px 4px; +} + +.resizer { + background: rgba(0, 0, 0, 0.5); + cursor: col-resize; + height: 100%; + position: absolute; + right: 0; + top: 0; + touch-action: none; + user-select: none; + width: 5px; +} + +.resizer.isResizing { + background: blue; + opacity: 1; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/column-pinning-sticky/src/main.ts b/examples/svelte/column-pinning-sticky/src/main.ts new file mode 100644 index 0000000000..aac57e409c --- /dev/null +++ b/examples/svelte/column-pinning-sticky/src/main.ts @@ -0,0 +1,9 @@ +// @ts-ignore -- svelte module types +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('root')!, +}) + +export default app diff --git a/examples/svelte/column-pinning-sticky/src/makeData.ts b/examples/svelte/column-pinning-sticky/src/makeData.ts new file mode 100644 index 0000000000..d274a17f45 --- /dev/null +++ b/examples/svelte/column-pinning-sticky/src/makeData.ts @@ -0,0 +1,47 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + return makeDataLevel() +} diff --git a/examples/svelte/column-pinning-sticky/svelte.config.js b/examples/svelte/column-pinning-sticky/svelte.config.js new file mode 100644 index 0000000000..8abe4369b8 --- /dev/null +++ b/examples/svelte/column-pinning-sticky/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/column-pinning-sticky/tsconfig.json b/examples/svelte/column-pinning-sticky/tsconfig.json new file mode 100644 index 0000000000..6761f6aaa4 --- /dev/null +++ b/examples/svelte/column-pinning-sticky/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/svelte/column-pinning-sticky/vite.config.js b/examples/svelte/column-pinning-sticky/vite.config.js new file mode 100644 index 0000000000..c6ced40a24 --- /dev/null +++ b/examples/svelte/column-pinning-sticky/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + svelte(), + ], +}) diff --git a/examples/svelte/column-pinning/index.html b/examples/svelte/column-pinning/index.html index 6ab1dd7e51..8441238e0e 100644 --- a/examples/svelte/column-pinning/index.html +++ b/examples/svelte/column-pinning/index.html @@ -4,7 +4,6 @@ Vite App - diff --git a/examples/svelte/column-pinning/package.json b/examples/svelte/column-pinning/package.json index d3269ccc3a..74de0044e3 100644 --- a/examples/svelte/column-pinning/package.json +++ b/examples/svelte/column-pinning/package.json @@ -1,23 +1,23 @@ { - "name": "tanstack-table-example-svelte-column-pinning", - "version": "0.0.0", + "name": "tanstack-svelte-table-example-column-pinning", "private": true, "type": "module", "scripts": { "dev": "vite", "build": "vite build", "serve": "vite preview", - "test:types": "svelte-check --tsconfig ./tsconfig.json" + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" }, "devDependencies": { - "@faker-js/faker": "^8.4.1", - "@rollup/plugin-replace": "^5.0.7", - "@sveltejs/vite-plugin-svelte": "^3.1.1", - "@tanstack/svelte-table": "^8.20.5", - "@tsconfig/svelte": "^5.0.4", - "svelte": "^4.2.18", - "svelte-check": "^3.8.4", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/svelte/column-pinning/src/App.svelte b/examples/svelte/column-pinning/src/App.svelte index ad1314ee32..11dcf6c044 100644 --- a/examples/svelte/column-pinning/src/App.svelte +++ b/examples/svelte/column-pinning/src/App.svelte @@ -1,50 +1,61 @@ -
-
-
+{#snippet headerCell(header: Header)} + +
+ {#if !header.isPlaceholder} + + {/if} +
+ {#if !header.isPlaceholder && header.column.getCanPin()} +
+ {#if header.column.getIsPinned() !== 'left'} + + {/if} + {#if header.column.getIsPinned()} + + {/if} + {#if header.column.getIsPinned() !== 'right'} + + {/if} +
+ {/if} + +{/snippet} + +
+
+ + +
+
+
- {#each $table.getAllLeafColumns() as column} -
+ {#each table.getAllLeafColumns() as column} +
{/each}
-
-
- + -
-
+
-
+
{#if isSplit} - +
- {#each $table.getLeftHeaderGroups() as headerGroup} + {#each table.getLeftHeaderGroups() as headerGroup (headerGroup.id) + } - {#each headerGroup.headers as header} - + {#each headerGroup.headers as header (header.id)} + {@render headerCell(header)} {/each} {/each} - {#each $table.getCoreRowModel().rows.slice(0, 20) as row} + {#each table.getCoreRowModel().rows.slice(0, 20) as row (row.id)} - {#each row.getLeftVisibleCells() as cell} + {#each row.getLeftVisibleCells() as cell (cell.id)} {/each} {/each}
-
- {#if !header.isPlaceholder} - - {/if} -
- {#if !header.isPlaceholder && header.column.getCanPin()} -
- {#if header.column.getIsPinned() !== 'left'} - - {/if} - {#if header.column.getIsPinned()} - - {/if} - {#if header.column.getIsPinned() !== 'right'} - - {/if} -
- {/if} -
- +
- {/if} - + {/if + } +
- {#each isSplit ? $table.getCenterHeaderGroups() : $table.getHeaderGroups() as headerGroup} + {#each isSplit ? table.getCenterHeaderGroups() : table.getHeaderGroups() as headerGroup (headerGroup.id) + } - {#each headerGroup.headers as header} - + {#each headerGroup.headers as header (header.id)} + {@render headerCell(header)} {/each} {/each} - {#each $table.getCoreRowModel().rows.slice(0, 20) as row} + {#each table.getCoreRowModel().rows.slice(0, 20) as row (row.id)} - {#each isSplit ? row.getCenterVisibleCells() : row.getVisibleCells() as cell} + {#each isSplit ? row.getCenterVisibleCells() : row.getVisibleCells() as cell (cell.id)} {/each} {/each}
-
- {#if !header.isPlaceholder} - - {/if} -
- {#if !header.isPlaceholder && header.column.getCanPin()} -
- {#if header.column.getIsPinned() !== 'left'} - - {/if} - {#if header.column.getIsPinned()} - - {/if} - {#if header.column.getIsPinned() !== 'right'} - - {/if} -
- {/if} -
- +
- {#if isSplit} - + {#if isSplit + } +
- {#each $table.getRightHeaderGroups() as headerGroup} + {#each table.getRightHeaderGroups() as headerGroup (headerGroup.id) + } - {#each headerGroup.headers as header} - + {#each headerGroup.headers as header (header.id)} + {@render headerCell(header)} {/each} {/each} - {#each $table.getRowModel().rows.slice(0, 20) as row} + {#each table.getRowModel().rows.slice(0, 20) as row (row.id)} - {#each row.getRightVisibleCells() as cell} + {#each row.getRightVisibleCells() as cell (cell.id)} {/each} {/each}
-
- {#if !header.isPlaceholder} - - {/if} -
- {#if !header.isPlaceholder && header.column.getCanPin()} -
- {#if header.column.getIsPinned() !== 'left'} - - {/if} - {#if header.column.getIsPinned()} - - {/if} - {#if header.column.getIsPinned() !== 'right'} - - {/if} -
- {/if} -
- +
- {/if} + {/if + }
-
{JSON.stringify($table.getState().columnPinning, null, 2)}
+
+
{JSON.stringify(table.store.state.columnPinning, null, 2)}
diff --git a/examples/svelte/column-pinning/src/index.css b/examples/svelte/column-pinning/src/index.css index 93034cdd1b..efd0b26417 100644 --- a/examples/svelte/column-pinning/src/index.css +++ b/examples/svelte/column-pinning/src/index.css @@ -5,6 +5,8 @@ html { table { border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; } tbody { @@ -33,3 +35,340 @@ tfoot { tfoot th { font-weight: normal; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/column-pinning/src/main.ts b/examples/svelte/column-pinning/src/main.ts index 4a5606b9a2..ba7a9641c3 100644 --- a/examples/svelte/column-pinning/src/main.ts +++ b/examples/svelte/column-pinning/src/main.ts @@ -1,7 +1,7 @@ -// @ts-ignore +import { mount } from 'svelte' import App from './App.svelte' -const app = new App({ +const app = mount(App, { target: document.getElementById('root')!, }) diff --git a/examples/svelte/column-pinning/src/makeData.ts b/examples/svelte/column-pinning/src/makeData.ts index 331dd1eb19..b9fb014aba 100644 --- a/examples/svelte/column-pinning/src/makeData.ts +++ b/examples/svelte/column-pinning/src/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,13 +29,13 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] return range(len).map((d): Person => { return { ...newPerson(), diff --git a/examples/svelte/column-pinning/tsconfig.json b/examples/svelte/column-pinning/tsconfig.json index e44d928411..6761f6aaa4 100644 --- a/examples/svelte/column-pinning/tsconfig.json +++ b/examples/svelte/column-pinning/tsconfig.json @@ -9,5 +9,13 @@ "checkJs": true, "isolatedModules": true }, - "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"] + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] } diff --git a/examples/svelte/column-resizing-performant/.gitignore b/examples/svelte/column-resizing-performant/.gitignore new file mode 100644 index 0000000000..2de1736a62 --- /dev/null +++ b/examples/svelte/column-resizing-performant/.gitignore @@ -0,0 +1,8 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local + +src/**/*.d.ts +src/**/*.map diff --git a/examples/svelte/column-resizing-performant/index.html b/examples/svelte/column-resizing-performant/index.html new file mode 100644 index 0000000000..8441238e0e --- /dev/null +++ b/examples/svelte/column-resizing-performant/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/svelte/column-resizing-performant/package.json b/examples/svelte/column-resizing-performant/package.json new file mode 100644 index 0000000000..01a46c905b --- /dev/null +++ b/examples/svelte/column-resizing-performant/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-svelte-table-example-column-resizing-performant", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" + }, + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/svelte/column-resizing-performant/src/App.svelte b/examples/svelte/column-resizing-performant/src/App.svelte new file mode 100644 index 0000000000..7fe5d770fc --- /dev/null +++ b/examples/svelte/column-resizing-performant/src/App.svelte @@ -0,0 +1,170 @@ + + +
+
+ + +
+ + This example has artificially slow cell renders to simulate complex usage + +
+
+    {JSON.stringify(table.store.state, null, 2)}
+  
+
+ ({data.length.toLocaleString()} rows) +
+
+
+ {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} +
+ {#each headerGroup.headers as header (header.id)} +
+ {#if !header.isPlaceholder} + + {/if} +
header.column.resetSize()} + onmousedown={header.getResizeHandler()} + ontouchstart={header.getResizeHandler()} + aria-hidden="true" + class="resizer {header.column.getIsResizing() + ? 'isResizing' + : ''}" + >
+
+ {/each} +
+ {/each} +
+
+ {#each table.getRowModel().rows as row (row.id)} +
+ {#each row.getAllCells() as cell (cell.id)} + {@const _ = (() => { + // simulate expensive render + for (const _i of Array.from({ length: 10000 })) { + Math.random() + } + })()} +
+ {cell.renderValue()} +
+ {/each} +
+ {/each} +
+
+
+
diff --git a/examples/svelte/column-resizing-performant/src/index.css b/examples/svelte/column-resizing-performant/src/index.css new file mode 100644 index 0000000000..78a395f530 --- /dev/null +++ b/examples/svelte/column-resizing-performant/src/index.css @@ -0,0 +1,411 @@ +* { + box-sizing: border-box; +} + +html { + font-family: sans-serif; + font-size: 14px; +} + +table, +.divTable { + border: 1px solid lightgray; + width: fit-content; + border-collapse: collapse; + border-spacing: 0; +} + +.tr { + display: flex; +} + +tr, +.tr { + width: fit-content; + height: 30px; +} + +th, +.th, +td, +.td { + box-shadow: inset 0 0 0 1px lightgray; + padding: 0.25rem; +} + +th, +.th { + padding: 2px 4px; + position: relative; + font-weight: bold; + text-align: center; + height: 30px; +} + +td, +.td { + height: 30px; +} + +.resizer { + position: absolute; + top: 0; + height: 100%; + width: 5px; + background: rgba(0, 0, 0, 0.5); + cursor: col-resize; + user-select: none; + touch-action: none; +} + +.resizer.isResizing { + background: blue; + opacity: 1; +} + +@media (hover: hover) { + .resizer { + opacity: 0; + } + + *:hover > .resizer { + opacity: 1; + } +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/column-resizing-performant/src/main.ts b/examples/svelte/column-resizing-performant/src/main.ts new file mode 100644 index 0000000000..aac57e409c --- /dev/null +++ b/examples/svelte/column-resizing-performant/src/main.ts @@ -0,0 +1,9 @@ +// @ts-ignore -- svelte module types +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('root')!, +}) + +export default app diff --git a/examples/svelte/column-resizing-performant/src/makeData.ts b/examples/svelte/column-resizing-performant/src/makeData.ts new file mode 100644 index 0000000000..d63c724b74 --- /dev/null +++ b/examples/svelte/column-resizing-performant/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/svelte/column-resizing-performant/svelte.config.js b/examples/svelte/column-resizing-performant/svelte.config.js new file mode 100644 index 0000000000..8abe4369b8 --- /dev/null +++ b/examples/svelte/column-resizing-performant/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/column-resizing-performant/tsconfig.json b/examples/svelte/column-resizing-performant/tsconfig.json new file mode 100644 index 0000000000..6761f6aaa4 --- /dev/null +++ b/examples/svelte/column-resizing-performant/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/svelte/column-resizing-performant/vite.config.js b/examples/svelte/column-resizing-performant/vite.config.js new file mode 100644 index 0000000000..c6ced40a24 --- /dev/null +++ b/examples/svelte/column-resizing-performant/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + svelte(), + ], +}) diff --git a/examples/svelte/column-resizing/.gitignore b/examples/svelte/column-resizing/.gitignore new file mode 100644 index 0000000000..2de1736a62 --- /dev/null +++ b/examples/svelte/column-resizing/.gitignore @@ -0,0 +1,8 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local + +src/**/*.d.ts +src/**/*.map diff --git a/examples/svelte/column-resizing/index.html b/examples/svelte/column-resizing/index.html new file mode 100644 index 0000000000..8441238e0e --- /dev/null +++ b/examples/svelte/column-resizing/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/svelte/column-resizing/package.json b/examples/svelte/column-resizing/package.json new file mode 100644 index 0000000000..b9be610bea --- /dev/null +++ b/examples/svelte/column-resizing/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-svelte-table-example-column-resizing", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" + }, + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/svelte/column-resizing/src/App.svelte b/examples/svelte/column-resizing/src/App.svelte new file mode 100644 index 0000000000..acd70ce285 --- /dev/null +++ b/examples/svelte/column-resizing/src/App.svelte @@ -0,0 +1,275 @@ + + +
+
+ + +
+ + +
+
+
{''} +
+
+ + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} + + {#each headerGroup.headers as header (header.id)} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getAllCells() as cell (cell.id)} + + {/each} + + {/each} + +
+ {#if !header.isPlaceholder} + + {/if} +
header.column.resetSize()} + onmousedown={header.getResizeHandler()} + ontouchstart={header.getResizeHandler()} + aria-hidden="true" + class="resizer {table.options + .columnResizeDirection} {header.column.getIsResizing() + ? 'isResizing' + : ''}" + style="transform: {resizerTransform(header)}" + >
+
+ +
+
+
+
{'
(relative)' + }
+
+
+
+ {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} +
+ {#each headerGroup.headers as header (header.id)} +
+ {#if !header.isPlaceholder} + + {/if} +
header.column.resetSize()} + onmousedown={header.getResizeHandler()} + ontouchstart={header.getResizeHandler()} + aria-hidden="true" + class="resizer {table.options + .columnResizeDirection} {header.column.getIsResizing() + ? 'isResizing' + : ''}" + style="transform: {resizerTransform(header)}" + >
+
+ {/each} +
+ {/each} +
+
+ {#each table.getRowModel().rows as row (row.id)} +
+ {#each row.getAllCells() as cell (cell.id)} +
+ +
+ {/each} +
+ {/each} +
+
+
+
+
{'
(absolute positioning)'}
+
+
+
+ {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} +
+ {#each headerGroup.headers as header (header.id)} +
+ {#if !header.isPlaceholder} + + {/if} +
header.column.resetSize()} + onmousedown={header.getResizeHandler()} + ontouchstart={header.getResizeHandler()} + aria-hidden="true" + class="resizer {table.options + .columnResizeDirection} {header.column.getIsResizing() + ? 'isResizing' + : ''}" + style="transform: {resizerTransform(header)}" + >
+
+ {/each} +
+ {/each} +
+
+ {#each table.getRowModel().rows as row (row.id)} +
+ {#each row.getAllCells() as cell (cell.id)} +
+ +
+ {/each} +
+ {/each} +
+
+
+
+
+
{JSON.stringify(table.store.state, null, 2)}
+
diff --git a/examples/svelte/column-resizing/src/index.css b/examples/svelte/column-resizing/src/index.css new file mode 100644 index 0000000000..d7f648b224 --- /dev/null +++ b/examples/svelte/column-resizing/src/index.css @@ -0,0 +1,419 @@ +* { + box-sizing: border-box; +} + +html { + font-family: sans-serif; + font-size: 14px; +} + +table, +.divTable { + border: 1px solid lightgray; + width: fit-content; + border-collapse: collapse; + border-spacing: 0; +} + +.tr { + display: flex; +} + +tr, +.tr { + width: fit-content; + height: 30px; +} + +th, +.th, +td, +.td { + box-shadow: inset 0 0 0 1px lightgray; + padding: 0.25rem; +} + +th, +.th { + padding: 2px 4px; + position: relative; + font-weight: bold; + text-align: center; + height: 30px; +} + +td, +.td { + height: 30px; +} + +.resizer { + position: absolute; + top: 0; + height: 100%; + width: 5px; + background: rgba(0, 0, 0, 0.5); + cursor: col-resize; + user-select: none; + touch-action: none; +} + +.resizer.ltr { + right: 0; +} + +.resizer.rtl { + left: 0; +} + +.resizer.isResizing { + background: blue; + opacity: 1; +} + +@media (hover: hover) { + .resizer { + opacity: 0; + } + + *:hover > .resizer { + opacity: 1; + } +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/column-resizing/src/main.ts b/examples/svelte/column-resizing/src/main.ts new file mode 100644 index 0000000000..aac57e409c --- /dev/null +++ b/examples/svelte/column-resizing/src/main.ts @@ -0,0 +1,9 @@ +// @ts-ignore -- svelte module types +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('root')!, +}) + +export default app diff --git a/examples/svelte/column-resizing/src/makeData.ts b/examples/svelte/column-resizing/src/makeData.ts new file mode 100644 index 0000000000..b9fb014aba --- /dev/null +++ b/examples/svelte/column-resizing/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/svelte/column-resizing/svelte.config.js b/examples/svelte/column-resizing/svelte.config.js new file mode 100644 index 0000000000..8abe4369b8 --- /dev/null +++ b/examples/svelte/column-resizing/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/column-resizing/tsconfig.json b/examples/svelte/column-resizing/tsconfig.json new file mode 100644 index 0000000000..6761f6aaa4 --- /dev/null +++ b/examples/svelte/column-resizing/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/svelte/column-resizing/vite.config.js b/examples/svelte/column-resizing/vite.config.js new file mode 100644 index 0000000000..c6ced40a24 --- /dev/null +++ b/examples/svelte/column-resizing/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + svelte(), + ], +}) diff --git a/examples/svelte/column-sizing/.gitignore b/examples/svelte/column-sizing/.gitignore new file mode 100644 index 0000000000..2de1736a62 --- /dev/null +++ b/examples/svelte/column-sizing/.gitignore @@ -0,0 +1,8 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local + +src/**/*.d.ts +src/**/*.map diff --git a/examples/svelte/column-sizing/index.html b/examples/svelte/column-sizing/index.html new file mode 100644 index 0000000000..8441238e0e --- /dev/null +++ b/examples/svelte/column-sizing/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/svelte/column-sizing/package.json b/examples/svelte/column-sizing/package.json new file mode 100644 index 0000000000..99ac4ba4e8 --- /dev/null +++ b/examples/svelte/column-sizing/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-svelte-table-example-column-sizing", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" + }, + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/svelte/column-sizing/src/App.svelte b/examples/svelte/column-sizing/src/App.svelte new file mode 100644 index 0000000000..a547912812 --- /dev/null +++ b/examples/svelte/column-sizing/src/App.svelte @@ -0,0 +1,205 @@ + + +
+
+ + +
+
+
Initial Column Sizes
+
+ {#each table.getAllColumns() as column} +
+ +
+ {/each} +
+
+
+
{''} +
+
+ + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} + + {#each headerGroup.headers as header (header.id)} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getAllCells() as cell (cell.id)} + + {/each} + + {/each} + +
+ {#if !header.isPlaceholder} + + {/if} +
+
+ +
+
+
+
{'
(relative)' + }
+
+
+
+ {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} +
+ {#each headerGroup.headers as header (header.id)} +
+ {#if !header.isPlaceholder} + + {/if} +
+
+ {/each} +
+ {/each} +
+
+ {#each table.getRowModel().rows as row (row.id)} +
+ {#each row.getAllCells() as cell (cell.id)} +
+ +
+ {/each} +
+ {/each} +
+
+
+
+
{'
(absolute positioning)'}
+
+
+
+ {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} +
+ {#each headerGroup.headers as header (header.id)} +
+ {#if !header.isPlaceholder} + + {/if} +
+
+ {/each} +
+ {/each} +
+
+ {#each table.getRowModel().rows as row (row.id)} +
+ {#each row.getAllCells() as cell (cell.id)} +
+ +
+ {/each} +
+ {/each} +
+
+
+
+
{JSON.stringify(table.store.state, null, 2)}
+
diff --git a/examples/svelte/column-sizing/src/index.css b/examples/svelte/column-sizing/src/index.css new file mode 100644 index 0000000000..5cd9906dc5 --- /dev/null +++ b/examples/svelte/column-sizing/src/index.css @@ -0,0 +1,396 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table, +.divTable { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +.tr { + display: flex; +} + +tr, +.tr { + width: fit-content; + height: 30px; +} + +th, +.th, +td, +.td { + box-shadow: inset 0 0 0 1px lightgray; + padding: 0.25rem; +} + +.th { + padding: 2px 4px; + position: relative; + font-weight: bold; + text-align: center; + height: 30px; +} + +.td { + height: 30px; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/column-sizing/src/main.ts b/examples/svelte/column-sizing/src/main.ts new file mode 100644 index 0000000000..aac57e409c --- /dev/null +++ b/examples/svelte/column-sizing/src/main.ts @@ -0,0 +1,9 @@ +// @ts-ignore -- svelte module types +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('root')!, +}) + +export default app diff --git a/examples/svelte/column-sizing/src/makeData.ts b/examples/svelte/column-sizing/src/makeData.ts new file mode 100644 index 0000000000..b9fb014aba --- /dev/null +++ b/examples/svelte/column-sizing/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/svelte/column-sizing/svelte.config.js b/examples/svelte/column-sizing/svelte.config.js new file mode 100644 index 0000000000..8abe4369b8 --- /dev/null +++ b/examples/svelte/column-sizing/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/column-sizing/tsconfig.json b/examples/svelte/column-sizing/tsconfig.json new file mode 100644 index 0000000000..6761f6aaa4 --- /dev/null +++ b/examples/svelte/column-sizing/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/svelte/column-sizing/vite.config.js b/examples/svelte/column-sizing/vite.config.js new file mode 100644 index 0000000000..c6ced40a24 --- /dev/null +++ b/examples/svelte/column-sizing/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + svelte(), + ], +}) diff --git a/examples/svelte/column-visibility/index.html b/examples/svelte/column-visibility/index.html index 6ab1dd7e51..8441238e0e 100644 --- a/examples/svelte/column-visibility/index.html +++ b/examples/svelte/column-visibility/index.html @@ -4,7 +4,6 @@ Vite App - diff --git a/examples/svelte/column-visibility/package.json b/examples/svelte/column-visibility/package.json index ced97ccf2e..9115e1c121 100644 --- a/examples/svelte/column-visibility/package.json +++ b/examples/svelte/column-visibility/package.json @@ -1,22 +1,23 @@ { - "name": "tanstack-table-example-svelte-column-visibility", - "version": "0.0.0", + "name": "tanstack-svelte-table-example-column-visibility", "private": true, "type": "module", "scripts": { "dev": "vite", "build": "vite build", "serve": "vite preview", - "test:types": "svelte-check --tsconfig ./tsconfig.json" + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" }, "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@sveltejs/vite-plugin-svelte": "^3.1.1", - "@tanstack/svelte-table": "^8.20.5", - "@tsconfig/svelte": "^5.0.4", - "svelte": "^4.2.18", - "svelte-check": "^3.8.4", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/svelte/column-visibility/src/App.svelte b/examples/svelte/column-visibility/src/App.svelte index 476bada33a..e9b3dcd0e1 100644 --- a/examples/svelte/column-visibility/src/App.svelte +++ b/examples/svelte/column-visibility/src/App.svelte @@ -1,82 +1,50 @@ -
-
-
+
+
+ + +
+
+
- {#each $table.getAllLeafColumns() as column} -
+ {#each table.getAllLeafColumns() as column} +
{/each}
-
+
- {#each $table.getHeaderGroups() as headerGroup} + {#each table.getHeaderGroups() as headerGroup (headerGroup.id) + } - {#each headerGroup.headers as header} + {#each headerGroup.headers as header (header.id)} {/each} @@ -188,30 +145,23 @@ {/each} - {#each $table.getCoreRowModel().rows.slice(0, 20) as row} + {#each table.getCoreRowModel().rows.slice(0, 20) as row (row.id)} - {#each row.getVisibleCells() as cell} + {#each row.getVisibleCells() as cell (cell.id)} {/each} {/each} - {#each $table.getFooterGroups() as footerGroup} + {#each table.getFooterGroups() as footerGroup (footerGroup.id)} - {#each footerGroup.headers as header} + {#each footerGroup.headers as header (header.id)} {/each} @@ -219,8 +169,7 @@ {/each}
{#if !header.isPlaceholder} - + {/if}
- +
{#if !header.isPlaceholder} - + {/if}
-
- -
-
{JSON.stringify($table.getState().columnVisibility, null, 2)}
+
+
{JSON.stringify(table.store.state.columnVisibility, null, 2)
+  }
diff --git a/examples/svelte/column-visibility/src/index.css b/examples/svelte/column-visibility/src/index.css index 43c09e0f6b..b9af6ded32 100644 --- a/examples/svelte/column-visibility/src/index.css +++ b/examples/svelte/column-visibility/src/index.css @@ -5,6 +5,8 @@ html { table { border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; } tbody { @@ -24,3 +26,340 @@ tfoot { tfoot th { font-weight: normal; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/column-visibility/src/main.ts b/examples/svelte/column-visibility/src/main.ts index 4a5606b9a2..aac57e409c 100644 --- a/examples/svelte/column-visibility/src/main.ts +++ b/examples/svelte/column-visibility/src/main.ts @@ -1,7 +1,8 @@ -// @ts-ignore +// @ts-ignore -- svelte module types +import { mount } from 'svelte' import App from './App.svelte' -const app = new App({ +const app = mount(App, { target: document.getElementById('root')!, }) diff --git a/examples/svelte/column-visibility/src/makeData.ts b/examples/svelte/column-visibility/src/makeData.ts new file mode 100644 index 0000000000..b9fb014aba --- /dev/null +++ b/examples/svelte/column-visibility/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/svelte/column-visibility/tsconfig.json b/examples/svelte/column-visibility/tsconfig.json index e44d928411..6761f6aaa4 100644 --- a/examples/svelte/column-visibility/tsconfig.json +++ b/examples/svelte/column-visibility/tsconfig.json @@ -9,5 +9,13 @@ "checkJs": true, "isolatedModules": true }, - "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"] + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] } diff --git a/examples/svelte/composable-tables/.gitignore b/examples/svelte/composable-tables/.gitignore new file mode 100644 index 0000000000..2de1736a62 --- /dev/null +++ b/examples/svelte/composable-tables/.gitignore @@ -0,0 +1,8 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local + +src/**/*.d.ts +src/**/*.map diff --git a/examples/svelte/composable-tables/index.html b/examples/svelte/composable-tables/index.html new file mode 100644 index 0000000000..29f9ba886c --- /dev/null +++ b/examples/svelte/composable-tables/index.html @@ -0,0 +1,13 @@ + + + + + + Svelte Composable Tables Example + + + +
+ + + diff --git a/examples/svelte/composable-tables/package.json b/examples/svelte/composable-tables/package.json new file mode 100644 index 0000000000..a82a02566c --- /dev/null +++ b/examples/svelte/composable-tables/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-svelte-table-example-composable-tables", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" + }, + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/svelte/composable-tables/src/App.svelte b/examples/svelte/composable-tables/src/App.svelte new file mode 100644 index 0000000000..959c23e6d6 --- /dev/null +++ b/examples/svelte/composable-tables/src/App.svelte @@ -0,0 +1,22 @@ + + +
+

Composable Tables Example

+

+ Both tables below use the same createAppTable hook and + shareable components, but with different data types and column + configurations. +

+ + + + +
+ + + +
diff --git a/examples/svelte/composable-tables/src/components/CategoryCell.svelte b/examples/svelte/composable-tables/src/components/CategoryCell.svelte new file mode 100644 index 0000000000..573d15a14e --- /dev/null +++ b/examples/svelte/composable-tables/src/components/CategoryCell.svelte @@ -0,0 +1,11 @@ + + + +{cell.getValue()} diff --git a/examples/svelte/composable-tables/src/components/ColumnFilter.svelte b/examples/svelte/composable-tables/src/components/ColumnFilter.svelte new file mode 100644 index 0000000000..fa576cc879 --- /dev/null +++ b/examples/svelte/composable-tables/src/components/ColumnFilter.svelte @@ -0,0 +1,22 @@ + + + +{#if header.column.getCanFilter()} + + +
e.stopPropagation()}> + header.column.setFilterValue(e.currentTarget.value)} + placeholder="Filter {header.column.id}..." + /> +
+{/if} diff --git a/examples/svelte/composable-tables/src/components/FooterColumnId.svelte b/examples/svelte/composable-tables/src/components/FooterColumnId.svelte new file mode 100644 index 0000000000..aa17e2761b --- /dev/null +++ b/examples/svelte/composable-tables/src/components/FooterColumnId.svelte @@ -0,0 +1,11 @@ + + + +{header.column.id} diff --git a/examples/svelte/composable-tables/src/components/FooterSum.svelte b/examples/svelte/composable-tables/src/components/FooterSum.svelte new file mode 100644 index 0000000000..1c68cbbe64 --- /dev/null +++ b/examples/svelte/composable-tables/src/components/FooterSum.svelte @@ -0,0 +1,19 @@ + + + +{sum > 0 ? sum.toLocaleString() : '—'} diff --git a/examples/svelte/composable-tables/src/components/NumberCell.svelte b/examples/svelte/composable-tables/src/components/NumberCell.svelte new file mode 100644 index 0000000000..96b18adca0 --- /dev/null +++ b/examples/svelte/composable-tables/src/components/NumberCell.svelte @@ -0,0 +1,11 @@ + + + +{cell.getValue().toLocaleString()} diff --git a/examples/svelte/composable-tables/src/components/PaginationControls.svelte b/examples/svelte/composable-tables/src/components/PaginationControls.svelte new file mode 100644 index 0000000000..ee6f9ad636 --- /dev/null +++ b/examples/svelte/composable-tables/src/components/PaginationControls.svelte @@ -0,0 +1,67 @@ + + + + diff --git a/examples/svelte/composable-tables/src/components/PriceCell.svelte b/examples/svelte/composable-tables/src/components/PriceCell.svelte new file mode 100644 index 0000000000..4d771a41da --- /dev/null +++ b/examples/svelte/composable-tables/src/components/PriceCell.svelte @@ -0,0 +1,16 @@ + + + + + ${cell.getValue().toLocaleString(undefined, { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })} + diff --git a/examples/svelte/composable-tables/src/components/ProductsTable.svelte b/examples/svelte/composable-tables/src/components/ProductsTable.svelte new file mode 100644 index 0000000000..eba8efbdf6 --- /dev/null +++ b/examples/svelte/composable-tables/src/components/ProductsTable.svelte @@ -0,0 +1,176 @@ + + + + +
+ + + + + + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id) + } + + {#each headerGroup.headers as h (h.id)} + + {#snippet children(header)} + + {/snippet} + + {/each + } + + {/each} + + + {#each rows as row (row.id)} + + {#each row.getAllCells() as c (c.id)} + + {#snippet children(cell)} + + {/snippet} + + {/each + } + + {/each} + + + {#each table.getFooterGroups() as footerGroup (footerGroup.id)} + + {#each footerGroup.headers as f (f.id)} + + {#snippet children(footer)} + + {/snippet} + + {/each + } + + {/each} + +
+ {#if !header.isPlaceholder} + + + + + {#if sorting.length > 1 && sorting.findIndex((s) => s.id === header.column.id) > -1} + + {sorting.findIndex((s) => s.id === header.column.id) + 1} + + {/if} + {/if} +
+ +
+ {#if !footer.isPlaceholder} + {#if footer.column.id === 'price' || footer.column.id === 'stock' || footer.column.id === 'rating'} + + {#if columnFilters.some((cf) => cf.id === footer.column.id)} + (filtered) + {/if} + {:else} + + {#if columnFilters.some((cf) => cf.id === footer.column.id)} + + {/if} + {/if} + {/if} +
+ + + + + + +
+
diff --git a/examples/svelte/composable-tables/src/components/ProgressCell.svelte b/examples/svelte/composable-tables/src/components/ProgressCell.svelte new file mode 100644 index 0000000000..1b50bff136 --- /dev/null +++ b/examples/svelte/composable-tables/src/components/ProgressCell.svelte @@ -0,0 +1,13 @@ + + + +
+
+
diff --git a/examples/svelte/composable-tables/src/components/RowActionsCell.svelte b/examples/svelte/composable-tables/src/components/RowActionsCell.svelte new file mode 100644 index 0000000000..87b70baa1a --- /dev/null +++ b/examples/svelte/composable-tables/src/components/RowActionsCell.svelte @@ -0,0 +1,30 @@ + + + +
+ + + +
diff --git a/examples/svelte/composable-tables/src/components/RowCount.svelte b/examples/svelte/composable-tables/src/components/RowCount.svelte new file mode 100644 index 0000000000..7cbf42bad7 --- /dev/null +++ b/examples/svelte/composable-tables/src/components/RowCount.svelte @@ -0,0 +1,15 @@ + + + +
+ Showing {table.getRowModel().rows.length.toLocaleString() + } of {table.getRowCount().toLocaleString()} rows +
diff --git a/examples/svelte/composable-tables/src/components/SortIndicator.svelte b/examples/svelte/composable-tables/src/components/SortIndicator.svelte new file mode 100644 index 0000000000..5bc7d031ed --- /dev/null +++ b/examples/svelte/composable-tables/src/components/SortIndicator.svelte @@ -0,0 +1,15 @@ + + + +{#if header.column.getIsSorted()} + + {header.column.getIsSorted() === 'asc' ? '🔼' : '🔽'} + +{/if} diff --git a/examples/svelte/composable-tables/src/components/StatusCell.svelte b/examples/svelte/composable-tables/src/components/StatusCell.svelte new file mode 100644 index 0000000000..eafb524662 --- /dev/null +++ b/examples/svelte/composable-tables/src/components/StatusCell.svelte @@ -0,0 +1,11 @@ + + + +{cell.getValue()} diff --git a/examples/svelte/composable-tables/src/components/TableToolbar.svelte b/examples/svelte/composable-tables/src/components/TableToolbar.svelte new file mode 100644 index 0000000000..6780cbe769 --- /dev/null +++ b/examples/svelte/composable-tables/src/components/TableToolbar.svelte @@ -0,0 +1,35 @@ + + + +
+

{title}

+
+ {#if onRefresh} + + {/if} + {#if onStressTest} + + {/if} + + +
+
diff --git a/examples/svelte/composable-tables/src/components/TextCell.svelte b/examples/svelte/composable-tables/src/components/TextCell.svelte new file mode 100644 index 0000000000..1ced128ee5 --- /dev/null +++ b/examples/svelte/composable-tables/src/components/TextCell.svelte @@ -0,0 +1,11 @@ + + + +{cell.getValue()} diff --git a/examples/svelte/composable-tables/src/components/UsersTable.svelte b/examples/svelte/composable-tables/src/components/UsersTable.svelte new file mode 100644 index 0000000000..ed85bff648 --- /dev/null +++ b/examples/svelte/composable-tables/src/components/UsersTable.svelte @@ -0,0 +1,190 @@ + + + + +
+ + + + + + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id) + } + + {#each headerGroup.headers as h (h.id)} + + {#snippet children(header)} + + {/snippet} + + {/each + } + + {/each} + + + {#each rows as row (row.id)} + + {#each row.getAllCells() as c (c.id)} + + {#snippet children(cell)} + + {/snippet} + + {/each + } + + {/each} + + + {#each table.getFooterGroups() as footerGroup (footerGroup.id)} + + {#each footerGroup.headers as f (f.id)} + + {#snippet children(footer)} + + {/snippet} + + {/each + } + + {/each} + +
+ {#if !header.isPlaceholder} + + + + + {#if sorting.length > 1 && sorting.findIndex((s) => s.id === header.column.id) > -1} + + {sorting.findIndex((s) => s.id === header.column.id) + 1} + + {/if} + {/if} +
+ +
+ {#if !footer.isPlaceholder} + {#if footer.column.id === 'age' || footer.column.id === 'visits' || footer.column.id === 'progress'} + + {#if columnFilters.some((cf) => cf.id === footer.column.id)} + (filtered) + {/if} + {:else if footer.column.id === 'actions'} + + {:else} + + {#if columnFilters.some((cf) => cf.id === footer.column.id)} + + {/if} + {/if} + {/if} +
+ + + + + + +
+
diff --git a/examples/svelte/composable-tables/src/hooks/table.ts b/examples/svelte/composable-tables/src/hooks/table.ts new file mode 100644 index 0000000000..71ad486cf5 --- /dev/null +++ b/examples/svelte/composable-tables/src/hooks/table.ts @@ -0,0 +1,99 @@ +/** + * Custom table hook setup using createTableHook + * + * This file creates a custom createAppTable hook with pre-bound components. + * Features, row models, and default options are defined once here and shared across all tables. + * Context hooks and a pre-bound createAppColumnHelper are also exported. + */ +import { + columnFilteringFeature, + createFilteredRowModel, + createPaginatedRowModel, + createSortedRowModel, + createTableHook, + filterFns, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/svelte-table' + +// Import table-level components +import PaginationControls from '../components/PaginationControls.svelte' +import RowCount from '../components/RowCount.svelte' +import TableToolbar from '../components/TableToolbar.svelte' + +// Import cell-level components +import CategoryCell from '../components/CategoryCell.svelte' +import NumberCell from '../components/NumberCell.svelte' +import PriceCell from '../components/PriceCell.svelte' +import ProgressCell from '../components/ProgressCell.svelte' +import RowActionsCell from '../components/RowActionsCell.svelte' +import StatusCell from '../components/StatusCell.svelte' +import TextCell from '../components/TextCell.svelte' + +// Import header/footer-level components (both use useHeaderContext) +import ColumnFilter from '../components/ColumnFilter.svelte' +import FooterColumnId from '../components/FooterColumnId.svelte' +import FooterSum from '../components/FooterSum.svelte' +import SortIndicator from '../components/SortIndicator.svelte' + +/** + * Create the custom table hook with all pre-bound components. + * This exports: + * - createAppColumnHelper: Create column definitions with TFeatures already bound + * - createAppTable: Hook for creating tables with TFeatures baked in + * - useTableContext: Access table instance in tableComponents + * - useCellContext: Access cell instance in cellComponents + * - useHeaderContext: Access header instance in headerComponents + */ +export const { + createAppColumnHelper, + createAppTable, + useTableContext, + useCellContext, + useHeaderContext, +} = createTableHook({ + // Features are set once here and shared across all tables + _features: tableFeatures({ + columnFilteringFeature, + rowPaginationFeature, + rowSortingFeature, + }), + + // Row models are set once here + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + }, + + // set any default table options here too + getRowId: (row) => row.id, + + // Register table-level components (accessible via table.ComponentName) + tableComponents: { + PaginationControls, + RowCount, + TableToolbar, + }, + + // Register cell-level components (accessible via cell.ComponentName in AppCell) + cellComponents: { + TextCell, + NumberCell, + StatusCell, + ProgressCell, + RowActionsCell, + PriceCell, + CategoryCell, + }, + + // Register header/footer-level components (accessible via header.ComponentName in AppHeader/AppFooter) + headerComponents: { + SortIndicator, + ColumnFilter, + FooterColumnId, + FooterSum, + }, +}) diff --git a/examples/svelte/composable-tables/src/index.css b/examples/svelte/composable-tables/src/index.css new file mode 100644 index 0000000000..4d8fef0142 --- /dev/null +++ b/examples/svelte/composable-tables/src/index.css @@ -0,0 +1,605 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + width: 100%; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th, +td { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 8px 12px; + text-align: left; +} + +th { + background-color: #f5f5f5; + font-weight: 600; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +.table-container { + padding: 16px; + max-width: 1200px; + margin: 0 auto; + border-collapse: collapse; + border-spacing: 0; +} + +.pagination { + display: flex; + align-items: center; + gap: 8px; + margin-top: 16px; + flex-wrap: wrap; +} + +.pagination button { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px 8px; + cursor: pointer; + background: white; +} + +.pagination button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.pagination input { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px; + width: 64px; +} + +.pagination select { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px; +} + +.sort-indicator { + margin-left: 4px; +} + +.column-filter { + margin-top: 4px; +} + +.column-filter input { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px; + width: 100%; + font-size: 12px; +} + +.sortable-header { + cursor: pointer; + user-select: none; +} + +.sortable-header:hover { + background-color: #e8e8e8; +} + +.row-actions { + display: flex; + gap: 4px; +} + +.row-actions button { + border: 1px solid #ccc; + border-radius: 4px; + padding: 2px 8px; + cursor: pointer; + background: white; + font-size: 12px; +} + +.row-actions button:hover { + background-color: #f0f0f0; +} + +.status-badge { + display: inline-block; + padding: 2px 8px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; +} + +.status-badge.relationship { + background-color: #d4edda; + color: #155724; +} + +.status-badge.complicated { + background-color: #fff3cd; + color: #856404; +} + +.status-badge.single { + background-color: #cce5ff; + color: #004085; +} + +.progress-bar { + width: 100%; + height: 8px; + background-color: #e9ecef; + border-radius: 4px; + overflow: hidden; +} + +.progress-bar-fill { + height: 100%; + background-color: #007bff; + transition: width 0.2s; +} + +.table-toolbar { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + flex-wrap: wrap; + gap: 8px; + border-collapse: collapse; + border-spacing: 0; +} + +.table-toolbar h2 { + margin: 0; + border-collapse: collapse; + border-spacing: 0; +} + +.table-toolbar-actions { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.table-toolbar button { + border: 1px solid #ccc; + border-radius: 4px; + padding: 8px 16px; + cursor: pointer; + background: white; + border-collapse: collapse; + border-spacing: 0; +} + +.table-toolbar button:hover { + background-color: #f0f0f0; + border-collapse: collapse; + border-spacing: 0; +} + +.row-count { + color: #666; + font-size: 14px; + margin-top: 8px; +} + +.app { + padding: 16px; +} + +.app h1 { + text-align: center; + margin-bottom: 8px; +} + +.description { + text-align: center; + color: #666; + margin-bottom: 32px; +} + +.description code { + background-color: #f5f5f5; + padding: 2px 6px; + border-radius: 4px; + font-size: 13px; +} + +.table-divider { + height: 48px; + border-bottom: 1px solid #e0e0e0; + margin: 32px auto; + max-width: 1200px; + border-collapse: collapse; + border-spacing: 0; +} + +.category-badge { + display: inline-block; + padding: 2px 8px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; + text-transform: capitalize; +} + +.category-badge.electronics { + background-color: #e3f2fd; + color: #1565c0; +} + +.category-badge.clothing { + background-color: #fce4ec; + color: #c2185b; +} + +.category-badge.food { + background-color: #e8f5e9; + color: #2e7d32; +} + +.category-badge.books { + background-color: #fff8e1; + color: #f57c00; +} + +.price { + font-weight: 600; + color: #2e7d32; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/composable-tables/src/main.ts b/examples/svelte/composable-tables/src/main.ts new file mode 100644 index 0000000000..aac57e409c --- /dev/null +++ b/examples/svelte/composable-tables/src/main.ts @@ -0,0 +1,9 @@ +// @ts-ignore -- svelte module types +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('root')!, +}) + +export default app diff --git a/examples/svelte/composable-tables/src/makeData.ts b/examples/svelte/composable-tables/src/makeData.ts new file mode 100644 index 0000000000..17dec1c6de --- /dev/null +++ b/examples/svelte/composable-tables/src/makeData.ts @@ -0,0 +1,77 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +export type Product = { + id: string + name: string + category: 'electronics' | 'clothing' | 'food' | 'books' + price: number + stock: number + rating: number +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +const newProduct = (): Product => { + return { + id: faker.string.uuid(), + name: faker.commerce.productName(), + category: faker.helpers.shuffle([ + 'electronics', + 'clothing', + 'food', + 'books', + ])[0], + price: parseFloat(faker.commerce.price({ min: 5, max: 500 })), + stock: faker.number.int({ min: 0, max: 200 }), + rating: faker.number.int({ min: 0, max: 100 }), + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} + +export function makeProductData(count: number): Array { + return range(count).map(() => newProduct()) +} diff --git a/examples/svelte/composable-tables/svelte.config.js b/examples/svelte/composable-tables/svelte.config.js new file mode 100644 index 0000000000..8abe4369b8 --- /dev/null +++ b/examples/svelte/composable-tables/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/composable-tables/tsconfig.json b/examples/svelte/composable-tables/tsconfig.json new file mode 100644 index 0000000000..6761f6aaa4 --- /dev/null +++ b/examples/svelte/composable-tables/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/svelte/composable-tables/vite.config.js b/examples/svelte/composable-tables/vite.config.js new file mode 100644 index 0000000000..c6ced40a24 --- /dev/null +++ b/examples/svelte/composable-tables/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + svelte(), + ], +}) diff --git a/examples/svelte/expanding/.gitignore b/examples/svelte/expanding/.gitignore new file mode 100644 index 0000000000..91c18232e2 --- /dev/null +++ b/examples/svelte/expanding/.gitignore @@ -0,0 +1,8 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local + +src/**/*.d.ts +src/**/*.map \ No newline at end of file diff --git a/examples/svelte/expanding/index.html b/examples/svelte/expanding/index.html new file mode 100644 index 0000000000..8441238e0e --- /dev/null +++ b/examples/svelte/expanding/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/svelte/expanding/package.json b/examples/svelte/expanding/package.json new file mode 100644 index 0000000000..15342963a2 --- /dev/null +++ b/examples/svelte/expanding/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-svelte-table-example-expanding", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" + }, + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/svelte/expanding/src/App.svelte b/examples/svelte/expanding/src/App.svelte new file mode 100644 index 0000000000..1838f38fca --- /dev/null +++ b/examples/svelte/expanding/src/App.svelte @@ -0,0 +1,302 @@ + + +
+
+ + +
+
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id) + } + + {#each headerGroup.headers as header (header.id)} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getAllCells() as cell (cell.id)} + + {/each} + + {/each} + +
+ {#if !header.isPlaceholder} +
+ {#if header.id === 'firstName'} + + {' '} + + {' '} + First Name + {:else} + + {/if} + {#if header.column.getCanFilter()} +
+ {@render Filter(header.column, table)} +
+ {/if} +
+ {/if} +
+ {#if cell.column.id === 'firstName'} +
+
+ + {' '} + {#if row.getCanExpand()} + + {:else} + {'\u{1F535}'} + {/if} + {' '} + +
+
+ {:else} + + {/if} +
+
+
+ + + + + +
Page
+ + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = (e.target as HTMLInputElement).value + ? Number((e.target as HTMLInputElement).value) - 1 + : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
+
{table.getRowModel().rows.length.toLocaleString()} Rows
+
+ + +
+
{JSON.stringify(table.state, null, 2)}
+
+ +{#snippet Filter(column: Column, table: SvelteTable)} + {@const firstValue = table + .getPreFilteredRowModel() + .flatRows[0]?.getValue(column.id)} + + {#if typeof firstValue === 'number'} +
+ + column.setFilterValue((old: [number, number] | undefined) => [ + (e.target as HTMLInputElement).value, + old?.[1], + ]) + } + placeholder="Min" + class="filter-input" + /> + + column.setFilterValue((old: [number, number] | undefined) => [ + old?.[0], + (e.target as HTMLInputElement).value, + ]) + } + placeholder="Max" + class="filter-input" + /> +
+ {:else} + column.setFilterValue((e.target as HTMLInputElement).value)} + placeholder="Search..." + class="filter-select" + /> + {/if} +{/snippet} diff --git a/examples/svelte/expanding/src/index.css b/examples/svelte/expanding/src/index.css new file mode 100644 index 0000000000..40a5eb73a4 --- /dev/null +++ b/examples/svelte/expanding/src/index.css @@ -0,0 +1,360 @@ +html { + font-family: sans-serif; + font-size: 14px; +} +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} +tbody { + border-bottom: 1px solid lightgray; +} +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} +tfoot { + color: gray; +} +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/expanding/src/main.ts b/examples/svelte/expanding/src/main.ts new file mode 100644 index 0000000000..aac57e409c --- /dev/null +++ b/examples/svelte/expanding/src/main.ts @@ -0,0 +1,9 @@ +// @ts-ignore -- svelte module types +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('root')!, +}) + +export default app diff --git a/examples/svelte/expanding/src/makeData.ts b/examples/svelte/expanding/src/makeData.ts new file mode 100644 index 0000000000..d63c724b74 --- /dev/null +++ b/examples/svelte/expanding/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/svelte/expanding/svelte.config.js b/examples/svelte/expanding/svelte.config.js new file mode 100644 index 0000000000..8abe4369b8 --- /dev/null +++ b/examples/svelte/expanding/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/expanding/tsconfig.json b/examples/svelte/expanding/tsconfig.json new file mode 100644 index 0000000000..6761f6aaa4 --- /dev/null +++ b/examples/svelte/expanding/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/svelte/expanding/vite.config.js b/examples/svelte/expanding/vite.config.js new file mode 100644 index 0000000000..c6ced40a24 --- /dev/null +++ b/examples/svelte/expanding/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + svelte(), + ], +}) diff --git a/examples/svelte/filtering/index.html b/examples/svelte/filtering/index.html index 6ab1dd7e51..8441238e0e 100644 --- a/examples/svelte/filtering/index.html +++ b/examples/svelte/filtering/index.html @@ -4,7 +4,6 @@ Vite App - diff --git a/examples/svelte/filtering/package.json b/examples/svelte/filtering/package.json index c5a084045e..cc35b2fe64 100644 --- a/examples/svelte/filtering/package.json +++ b/examples/svelte/filtering/package.json @@ -1,24 +1,23 @@ { - "name": "tanstack-table-example-svelte-filtering", - "version": "0.0.0", + "name": "tanstack-svelte-table-example-filtering", "private": true, "type": "module", "scripts": { "dev": "vite", "build": "vite build", "serve": "vite preview", - "test:types": "svelte-check --tsconfig ./tsconfig.json" + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" }, "devDependencies": { - "@faker-js/faker": "^8.4.1", - "@rollup/plugin-replace": "^5.0.7", - "@sveltejs/vite-plugin-svelte": "^3.1.1", - "@tanstack/match-sorter-utils": "^8.19.4", - "@tanstack/svelte-table": "^8.20.5", - "@tsconfig/svelte": "^5.0.4", - "svelte": "^4.2.18", - "svelte-check": "^3.8.4", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/svelte/filtering/src/App.svelte b/examples/svelte/filtering/src/App.svelte index 9a7f5de097..68cacca4de 100644 --- a/examples/svelte/filtering/src/App.svelte +++ b/examples/svelte/filtering/src/App.svelte @@ -1,111 +1,226 @@ - -
- - - {#each $table.getHeaderGroups() as headerGroup} - - {#each headerGroup.headers as header, idx} - - {/each} - - {/each} - - - {#each $table.getRowModel().rows as row} - - {#each row.getVisibleCells() as cell} - - {/each} - - {/each} - -
- {#if !header.isPlaceholder} - - {/if} -
- -
-
-
"globalFilter": "{$table.getState().globalFilter}"
+
+
+ + +
+ table.setGlobalFilter(String(value))} + class="summary-panel" + placeholder="Search all columns..." + /> +
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id) + } + + {#each headerGroup.headers as header (header.id)} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getAllCells() as cell (cell.id)} + + {/each} + + {/each} + +
+ {#if !header.isPlaceholder} + + {#if header.column.getCanFilter()} +
+ +
+ {/if} + {/if} +
+ +
+
+
+ + + + + +
Page
+ + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = (e.target as HTMLInputElement).value + ? Number((e.target as HTMLInputElement).value) - 1 + : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
+
+ Showing {table.getRowModel().rows.length.toLocaleString()} of{' '} + {table.getPrePaginatedRowModel().rows.length.toLocaleString()} Rows +
+
{JSON.stringify(table.state, null, 2)}
+
diff --git a/examples/svelte/filtering/src/ColumnFilter.svelte b/examples/svelte/filtering/src/ColumnFilter.svelte new file mode 100644 index 0000000000..a9633243ae --- /dev/null +++ b/examples/svelte/filtering/src/ColumnFilter.svelte @@ -0,0 +1,69 @@ + + +{#if isNumber +} +
+
+ + column.setFilterValue((old: [number, number] | undefined) => [ + value, + old?.[1] ?? '', + ]) + } + debounce={500} + placeholder="Min" + class="filter-input" + /> + + column.setFilterValue((old: [number, number] | undefined) => [ + old?.[0] ?? '', + value, + ]) + } + debounce={500} + placeholder="Max" + class="filter-input" + /> +
+
+{:else} +
+ column.setFilterValue(value)} + debounce={500} + placeholder="Search..." + class="filter-select" + list="{column.id}list" + /> +
+{/if} diff --git a/examples/svelte/filtering/src/DebouncedInput.svelte b/examples/svelte/filtering/src/DebouncedInput.svelte new file mode 100644 index 0000000000..75e9c11427 --- /dev/null +++ b/examples/svelte/filtering/src/DebouncedInput.svelte @@ -0,0 +1,29 @@ + + + diff --git a/examples/svelte/filtering/src/index.css b/examples/svelte/filtering/src/index.css index 43c09e0f6b..b9af6ded32 100644 --- a/examples/svelte/filtering/src/index.css +++ b/examples/svelte/filtering/src/index.css @@ -5,6 +5,8 @@ html { table { border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; } tbody { @@ -24,3 +26,340 @@ tfoot { tfoot th { font-weight: normal; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/filtering/src/main.ts b/examples/svelte/filtering/src/main.ts index 61bf4ae49f..aac57e409c 100644 --- a/examples/svelte/filtering/src/main.ts +++ b/examples/svelte/filtering/src/main.ts @@ -1,20 +1,8 @@ -// @ts-ignore +// @ts-ignore -- svelte module types +import { mount } from 'svelte' import App from './App.svelte' -import type { FilterFn } from '@tanstack/svelte-table' - -import type { RankingInfo } from '@tanstack/match-sorter-utils' - -declare module '@tanstack/svelte-table' { - interface FilterFns { - fuzzy: FilterFn - } - interface FilterMeta { - itemRank: RankingInfo - } -} - -const app = new App({ +const app = mount(App, { target: document.getElementById('root')!, }) diff --git a/examples/svelte/filtering/src/makeData.ts b/examples/svelte/filtering/src/makeData.ts index 331dd1eb19..b9fb014aba 100644 --- a/examples/svelte/filtering/src/makeData.ts +++ b/examples/svelte/filtering/src/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,13 +29,13 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] return range(len).map((d): Person => { return { ...newPerson(), diff --git a/examples/svelte/filtering/tsconfig.json b/examples/svelte/filtering/tsconfig.json index e44d928411..6761f6aaa4 100644 --- a/examples/svelte/filtering/tsconfig.json +++ b/examples/svelte/filtering/tsconfig.json @@ -9,5 +9,13 @@ "checkJs": true, "isolatedModules": true }, - "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"] + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] } diff --git a/examples/svelte/filters-faceted/.gitignore b/examples/svelte/filters-faceted/.gitignore new file mode 100644 index 0000000000..2de1736a62 --- /dev/null +++ b/examples/svelte/filters-faceted/.gitignore @@ -0,0 +1,8 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local + +src/**/*.d.ts +src/**/*.map diff --git a/examples/svelte/filters-faceted/README.md b/examples/svelte/filters-faceted/README.md new file mode 100644 index 0000000000..b168d3c4b1 --- /dev/null +++ b/examples/svelte/filters-faceted/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run start` or `yarn start` diff --git a/examples/svelte/filters-faceted/index.html b/examples/svelte/filters-faceted/index.html new file mode 100644 index 0000000000..8441238e0e --- /dev/null +++ b/examples/svelte/filters-faceted/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/svelte/filters-faceted/package.json b/examples/svelte/filters-faceted/package.json new file mode 100644 index 0000000000..80fe08c7e4 --- /dev/null +++ b/examples/svelte/filters-faceted/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-svelte-table-example-filters-faceted", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" + }, + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/svelte/filters-faceted/src/App.svelte b/examples/svelte/filters-faceted/src/App.svelte new file mode 100644 index 0000000000..22e70369c8 --- /dev/null +++ b/examples/svelte/filters-faceted/src/App.svelte @@ -0,0 +1,223 @@ + + +
+
+ + +
+ table.setGlobalFilter(String(value))} + class="summary-panel" + placeholder="Search all columns..." + /> +
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id) + } + + {#each headerGroup.headers as header (header.id)} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getAllCells() as cell (cell.id)} + + {/each} + + {/each} + +
+ {#if !header.isPlaceholder} + + {#if header.column.getCanFilter()} +
+ +
+ {/if} + {/if} +
+ +
+
+
+ + + + + +
Page
+ + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = (e.target as HTMLInputElement).value + ? Number((e.target as HTMLInputElement).value) - 1 + : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
+
+ Showing {table.getRowModel().rows.length.toLocaleString()} of{' '} + {table.getPrePaginatedRowModel().rows.length.toLocaleString()} Rows +
+
{JSON.stringify(table.state, null, 2)}
+
diff --git a/examples/svelte/filters-faceted/src/ColumnFilter.svelte b/examples/svelte/filters-faceted/src/ColumnFilter.svelte new file mode 100644 index 0000000000..5345f90551 --- /dev/null +++ b/examples/svelte/filters-faceted/src/ColumnFilter.svelte @@ -0,0 +1,78 @@ + + +{#if isNumber} +
+
+ + column.setFilterValue((old: [number, number]) => [ + value, + old?.[1], + ]) + } + debounce={500} + placeholder={`Min ${column.getFacetedMinMaxValues()?.[0] ? `(${column.getFacetedMinMaxValues()?.[0]})` : ''}`} + class="filter-input" + /> + + column.setFilterValue((old: [number, number]) => [ + old?.[0], + value, + ]) + } + debounce={500} + placeholder={`Max ${column.getFacetedMinMaxValues()?.[1] ? `(${column.getFacetedMinMaxValues()?.[1]})` : ''}`} + class="filter-input" + /> +
+
+{:else} +
+ + {#each getSortedUniqueValues().slice(0, 5000) as value} + + {/each} + + column.setFilterValue(value)} + debounce={500} + placeholder={`Search... (${column.getFacetedUniqueValues().size})`} + class="filter-select" + list="{column.id}list" + /> +
+{/if} diff --git a/examples/svelte/filters-faceted/src/DebouncedInput.svelte b/examples/svelte/filters-faceted/src/DebouncedInput.svelte new file mode 100644 index 0000000000..75e9c11427 --- /dev/null +++ b/examples/svelte/filters-faceted/src/DebouncedInput.svelte @@ -0,0 +1,29 @@ + + + diff --git a/examples/svelte/filters-faceted/src/index.css b/examples/svelte/filters-faceted/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/svelte/filters-faceted/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/filters-faceted/src/main.ts b/examples/svelte/filters-faceted/src/main.ts new file mode 100644 index 0000000000..aac57e409c --- /dev/null +++ b/examples/svelte/filters-faceted/src/main.ts @@ -0,0 +1,9 @@ +// @ts-ignore -- svelte module types +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('root')!, +}) + +export default app diff --git a/examples/svelte/filters-faceted/src/makeData.ts b/examples/svelte/filters-faceted/src/makeData.ts new file mode 100644 index 0000000000..b9fb014aba --- /dev/null +++ b/examples/svelte/filters-faceted/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/svelte/filters-faceted/svelte.config.js b/examples/svelte/filters-faceted/svelte.config.js new file mode 100644 index 0000000000..8abe4369b8 --- /dev/null +++ b/examples/svelte/filters-faceted/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/filters-faceted/tsconfig.json b/examples/svelte/filters-faceted/tsconfig.json new file mode 100644 index 0000000000..6761f6aaa4 --- /dev/null +++ b/examples/svelte/filters-faceted/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/svelte/filters-faceted/vite.config.js b/examples/svelte/filters-faceted/vite.config.js new file mode 100644 index 0000000000..c6ced40a24 --- /dev/null +++ b/examples/svelte/filters-faceted/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + svelte(), + ], +}) diff --git a/examples/svelte/filters-fuzzy/.gitignore b/examples/svelte/filters-fuzzy/.gitignore new file mode 100644 index 0000000000..2de1736a62 --- /dev/null +++ b/examples/svelte/filters-fuzzy/.gitignore @@ -0,0 +1,8 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local + +src/**/*.d.ts +src/**/*.map diff --git a/examples/svelte/filters-fuzzy/README.md b/examples/svelte/filters-fuzzy/README.md new file mode 100644 index 0000000000..b168d3c4b1 --- /dev/null +++ b/examples/svelte/filters-fuzzy/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run start` or `yarn start` diff --git a/examples/svelte/filters-fuzzy/index.html b/examples/svelte/filters-fuzzy/index.html new file mode 100644 index 0000000000..8441238e0e --- /dev/null +++ b/examples/svelte/filters-fuzzy/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/svelte/filters-fuzzy/package.json b/examples/svelte/filters-fuzzy/package.json new file mode 100644 index 0000000000..bf8e079ba8 --- /dev/null +++ b/examples/svelte/filters-fuzzy/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-svelte-table-example-filters-fuzzy", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" + }, + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/match-sorter-utils": "^9.0.0-alpha.4", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/svelte/filters-fuzzy/src/App.svelte b/examples/svelte/filters-fuzzy/src/App.svelte new file mode 100644 index 0000000000..6034180890 --- /dev/null +++ b/examples/svelte/filters-fuzzy/src/App.svelte @@ -0,0 +1,257 @@ + + +
+
+ + +
+
+ table.setGlobalFilter(String(value))} + class="summary-panel" + placeholder="Search all columns..." + /> +
+
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id) + } + + {#each headerGroup.headers as header (header.id)} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getAllCells() as cell (cell.id)} + + {/each} + + {/each} + +
+ {#if !header.isPlaceholder} +
{ + if (e.key === 'Enter' || e.key === ' ') { + header.column.getToggleSortingHandler()?.(e) + } + }} + > + + {#if header.column.getIsSorted() === 'asc'} + {' '}🔼 + {:else if header.column.getIsSorted() === 'desc'} + {' '}🔽 + {/if} +
+ {#if header.column.getCanFilter()} +
+ header.column.setFilterValue(value)} + placeholder="Search..." + class="filter-select" + /> +
+ {/if} + {/if} +
+ +
+
+
+ + + + + +
Page
+ + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = e.currentTarget.value + ? Number(e.currentTarget.value) - 1 + : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
+
{table.getPrePaginatedRowModel().rows.length.toLocaleString()} Rows
+
+ + +
+
{JSON.stringify(table.state, null, 2)}
+
diff --git a/examples/svelte/filters-fuzzy/src/DebouncedInput.svelte b/examples/svelte/filters-fuzzy/src/DebouncedInput.svelte new file mode 100644 index 0000000000..75e9c11427 --- /dev/null +++ b/examples/svelte/filters-fuzzy/src/DebouncedInput.svelte @@ -0,0 +1,29 @@ + + + diff --git a/examples/svelte/filters-fuzzy/src/index.css b/examples/svelte/filters-fuzzy/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/svelte/filters-fuzzy/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/filters-fuzzy/src/main.ts b/examples/svelte/filters-fuzzy/src/main.ts new file mode 100644 index 0000000000..c661297a96 --- /dev/null +++ b/examples/svelte/filters-fuzzy/src/main.ts @@ -0,0 +1,20 @@ +// @ts-ignore -- svelte module types +import { mount } from 'svelte' +import App from './App.svelte' +import type { FilterFn } from '@tanstack/svelte-table' +import type { RankingInfo } from '@tanstack/match-sorter-utils' + +declare module '@tanstack/svelte-table' { + interface FilterFns { + fuzzy: FilterFn + } + interface FilterMeta { + itemRank?: RankingInfo + } +} + +const app = mount(App, { + target: document.getElementById('root')!, +}) + +export default app diff --git a/examples/svelte/filters-fuzzy/src/makeData.ts b/examples/svelte/filters-fuzzy/src/makeData.ts new file mode 100644 index 0000000000..38c1db1f15 --- /dev/null +++ b/examples/svelte/filters-fuzzy/src/makeData.ts @@ -0,0 +1,45 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + id: number + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (num: number): Person => ({ + id: num, + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (index): Person => ({ + ...newPerson(index), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/svelte/filters-fuzzy/svelte.config.js b/examples/svelte/filters-fuzzy/svelte.config.js new file mode 100644 index 0000000000..8abe4369b8 --- /dev/null +++ b/examples/svelte/filters-fuzzy/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/filters-fuzzy/tsconfig.json b/examples/svelte/filters-fuzzy/tsconfig.json new file mode 100644 index 0000000000..6761f6aaa4 --- /dev/null +++ b/examples/svelte/filters-fuzzy/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/svelte/filters-fuzzy/vite.config.js b/examples/svelte/filters-fuzzy/vite.config.js new file mode 100644 index 0000000000..c6ced40a24 --- /dev/null +++ b/examples/svelte/filters-fuzzy/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + svelte(), + ], +}) diff --git a/examples/svelte/grouping/.gitignore b/examples/svelte/grouping/.gitignore new file mode 100644 index 0000000000..2de1736a62 --- /dev/null +++ b/examples/svelte/grouping/.gitignore @@ -0,0 +1,8 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local + +src/**/*.d.ts +src/**/*.map diff --git a/examples/svelte/grouping/index.html b/examples/svelte/grouping/index.html new file mode 100644 index 0000000000..8441238e0e --- /dev/null +++ b/examples/svelte/grouping/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/svelte/grouping/package.json b/examples/svelte/grouping/package.json new file mode 100644 index 0000000000..4af5d182f4 --- /dev/null +++ b/examples/svelte/grouping/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-svelte-table-example-grouping", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" + }, + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/svelte/grouping/src/App.svelte b/examples/svelte/grouping/src/App.svelte new file mode 100644 index 0000000000..1eda308971 --- /dev/null +++ b/examples/svelte/grouping/src/App.svelte @@ -0,0 +1,242 @@ + + +
+
+ + +
+
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id) + } + + {#each headerGroup.headers as header (header.id)} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getAllCells() as cell (cell.id)} + + {/each} + + {/each} + +
+ {#if !header.isPlaceholder} +
+ {#if header.column.getCanGroup()} + + {/if} + {' '} + +
+ {/if} +
+ {#if cell.getIsGrouped()} + + {:else if cell.getIsAggregated()} + + {:else if !cell.getIsPlaceholder()} + + {/if} +
+
+
+ + + + + +
Page
+ + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = (e.target as HTMLInputElement).value + ? Number((e.target as HTMLInputElement).value) - 1 + : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
+
{table.getRowModel().rows.length.toLocaleString()} Rows
+
+ + +
+
{JSON.stringify(table.state, null, 2)}
+
diff --git a/examples/svelte/grouping/src/index.css b/examples/svelte/grouping/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/svelte/grouping/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/grouping/src/main.ts b/examples/svelte/grouping/src/main.ts new file mode 100644 index 0000000000..aac57e409c --- /dev/null +++ b/examples/svelte/grouping/src/main.ts @@ -0,0 +1,9 @@ +// @ts-ignore -- svelte module types +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('root')!, +}) + +export default app diff --git a/examples/svelte/grouping/src/makeData.ts b/examples/svelte/grouping/src/makeData.ts new file mode 100644 index 0000000000..3f9a8b9181 --- /dev/null +++ b/examples/svelte/grouping/src/makeData.ts @@ -0,0 +1,47 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/svelte/grouping/svelte.config.js b/examples/svelte/grouping/svelte.config.js new file mode 100644 index 0000000000..8abe4369b8 --- /dev/null +++ b/examples/svelte/grouping/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/grouping/tsconfig.json b/examples/svelte/grouping/tsconfig.json new file mode 100644 index 0000000000..6761f6aaa4 --- /dev/null +++ b/examples/svelte/grouping/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/svelte/grouping/vite.config.js b/examples/svelte/grouping/vite.config.js new file mode 100644 index 0000000000..c6ced40a24 --- /dev/null +++ b/examples/svelte/grouping/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + svelte(), + ], +}) diff --git a/examples/svelte/pagination/.gitignore b/examples/svelte/pagination/.gitignore new file mode 100644 index 0000000000..2de1736a62 --- /dev/null +++ b/examples/svelte/pagination/.gitignore @@ -0,0 +1,8 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local + +src/**/*.d.ts +src/**/*.map diff --git a/examples/svelte/pagination/index.html b/examples/svelte/pagination/index.html new file mode 100644 index 0000000000..8441238e0e --- /dev/null +++ b/examples/svelte/pagination/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/svelte/pagination/package.json b/examples/svelte/pagination/package.json new file mode 100644 index 0000000000..938d8d0e5c --- /dev/null +++ b/examples/svelte/pagination/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-svelte-table-example-pagination", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" + }, + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/svelte/pagination/src/App.svelte b/examples/svelte/pagination/src/App.svelte new file mode 100644 index 0000000000..887924315a --- /dev/null +++ b/examples/svelte/pagination/src/App.svelte @@ -0,0 +1,180 @@ + + +
+
+ + +
+
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id) + } + + {#each headerGroup.headers as header (header.id)} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getAllCells() as cell (cell.id)} + + {/each} + + {/each} + +
+
+ {#if !header.isPlaceholder} + + {/if} +
+
+ +
+
+
+ + + + + +
Page
+ + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = (e.target as HTMLInputElement).value + ? Number((e.target as HTMLInputElement).value) - 1 + : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
+
+ Showing {table.getRowModel().rows.length.toLocaleString()} of{' '} + {table.getRowCount().toLocaleString()} Rows +
+
+
+ + +
+
{JSON.stringify(table.state, null, 2)}
+
diff --git a/examples/svelte/pagination/src/index.css b/examples/svelte/pagination/src/index.css new file mode 100644 index 0000000000..40a5eb73a4 --- /dev/null +++ b/examples/svelte/pagination/src/index.css @@ -0,0 +1,360 @@ +html { + font-family: sans-serif; + font-size: 14px; +} +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} +tbody { + border-bottom: 1px solid lightgray; +} +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} +tfoot { + color: gray; +} +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/pagination/src/main.ts b/examples/svelte/pagination/src/main.ts new file mode 100644 index 0000000000..aac57e409c --- /dev/null +++ b/examples/svelte/pagination/src/main.ts @@ -0,0 +1,9 @@ +// @ts-ignore -- svelte module types +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('root')!, +}) + +export default app diff --git a/examples/svelte/pagination/src/makeData.ts b/examples/svelte/pagination/src/makeData.ts new file mode 100644 index 0000000000..d63c724b74 --- /dev/null +++ b/examples/svelte/pagination/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/svelte/pagination/svelte.config.js b/examples/svelte/pagination/svelte.config.js new file mode 100644 index 0000000000..8abe4369b8 --- /dev/null +++ b/examples/svelte/pagination/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/pagination/tsconfig.json b/examples/svelte/pagination/tsconfig.json new file mode 100644 index 0000000000..6761f6aaa4 --- /dev/null +++ b/examples/svelte/pagination/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/svelte/pagination/vite.config.js b/examples/svelte/pagination/vite.config.js new file mode 100644 index 0000000000..c6ced40a24 --- /dev/null +++ b/examples/svelte/pagination/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + svelte(), + ], +}) diff --git a/examples/svelte/row-pinning/.gitignore b/examples/svelte/row-pinning/.gitignore new file mode 100644 index 0000000000..2de1736a62 --- /dev/null +++ b/examples/svelte/row-pinning/.gitignore @@ -0,0 +1,8 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local + +src/**/*.d.ts +src/**/*.map diff --git a/examples/svelte/row-pinning/index.html b/examples/svelte/row-pinning/index.html new file mode 100644 index 0000000000..8441238e0e --- /dev/null +++ b/examples/svelte/row-pinning/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/svelte/row-pinning/package.json b/examples/svelte/row-pinning/package.json new file mode 100644 index 0000000000..7c07d8ff6e --- /dev/null +++ b/examples/svelte/row-pinning/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-svelte-table-example-row-pinning", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" + }, + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/svelte/row-pinning/src/App.svelte b/examples/svelte/row-pinning/src/App.svelte new file mode 100644 index 0000000000..f170764e62 --- /dev/null +++ b/examples/svelte/row-pinning/src/App.svelte @@ -0,0 +1,395 @@ + + +{#snippet PinCell(row: ReturnType['rows'][0])} + {#if row.getIsPinned()} + + {:else} +
+ + +
+ {/if} +{/snippet} + +{#snippet ExpandCell(row: ReturnType['rows'][0])} +
+ {#if row.getCanExpand()} + + {:else} + * + {/if} +
+{/snippet} + +{#snippet ExpandAllButton()} + +{/snippet} + +{#snippet PinnedRow(row: ReturnType['rows'][0])} + + {#each row.getAllCells() as cell (cell.id)} + + {#if cell.column.id === 'pin'} + {@render PinCell(row)} + {:else if cell.column.id === 'firstName'} + {@render ExpandCell(row)} + + {:else} + + {/if} + + {/each} + +{/snippet} + +{#snippet Filter(column: Column, tableRef: SvelteTable)} + {@const firstValue = tableRef + .getPreFilteredRowModel() + .flatRows[0]?.getValue(column.id)} + + {#if typeof firstValue === 'number'} +
+ + column.setFilterValue((old: any) => [(e.target as HTMLInputElement).value, old?.[1]]) + } + placeholder="Min" + class="filter-input" + /> + + column.setFilterValue((old: any) => [old?.[0], (e.target as HTMLInputElement).value]) + } + placeholder="Max" + class="filter-input" + /> +
+ {:else} + column.setFilterValue((e.target as HTMLInputElement).value)} + placeholder="Search..." + class="filter-select" + /> + {/if} +{/snippet} + +
+
+
+ + +
+
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id) + } + + {#each headerGroup.headers as header (header.id)} + + {/each} + + {/each} + + + {#each table.getTopRows() as row (row.id)} + {@render PinnedRow(row)} + {/each} + {#each copyPinnedRows ? table.getRowModel().rows : table.getCenterRows() as row (row.id)} + + {#each row.getAllCells() as cell (cell.id)} + + {/each} + + {/each} + {#each table.getBottomRows() as row (row.id)} + {@render PinnedRow(row)} + {/each} + +
+ {#if !header.isPlaceholder} + {#if header.column.id === 'firstName'} + {@render ExpandAllButton()} + {:else} + + {/if} + {#if header.column.getCanFilter()} +
+ {@render Filter(header.column, table)} +
+ {/if} + {/if} +
+ {#if cell.column.id === 'pin'} + {@render PinCell(row)} + {:else if cell.column.id === 'firstName'} + {@render ExpandCell(row)} + + {:else} + + {/if} +
+
+ +
+
+ + + + + +
Page
+ + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = (e.target as HTMLInputElement).value + ? Number((e.target as HTMLInputElement).value) - 1 + : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ + +
+
{JSON.stringify(table.state.rowPinning, null, 2)}
+
diff --git a/examples/svelte/row-pinning/src/index.css b/examples/svelte/row-pinning/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/svelte/row-pinning/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/row-pinning/src/main.ts b/examples/svelte/row-pinning/src/main.ts new file mode 100644 index 0000000000..aac57e409c --- /dev/null +++ b/examples/svelte/row-pinning/src/main.ts @@ -0,0 +1,9 @@ +// @ts-ignore -- svelte module types +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('root')!, +}) + +export default app diff --git a/examples/svelte/row-pinning/src/makeData.ts b/examples/svelte/row-pinning/src/makeData.ts new file mode 100644 index 0000000000..d63c724b74 --- /dev/null +++ b/examples/svelte/row-pinning/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/svelte/row-pinning/svelte.config.js b/examples/svelte/row-pinning/svelte.config.js new file mode 100644 index 0000000000..8abe4369b8 --- /dev/null +++ b/examples/svelte/row-pinning/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/row-pinning/tsconfig.json b/examples/svelte/row-pinning/tsconfig.json new file mode 100644 index 0000000000..6761f6aaa4 --- /dev/null +++ b/examples/svelte/row-pinning/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/svelte/row-pinning/vite.config.js b/examples/svelte/row-pinning/vite.config.js new file mode 100644 index 0000000000..c6ced40a24 --- /dev/null +++ b/examples/svelte/row-pinning/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + svelte(), + ], +}) diff --git a/examples/svelte/row-selection/index.html b/examples/svelte/row-selection/index.html new file mode 100644 index 0000000000..73faa6088f --- /dev/null +++ b/examples/svelte/row-selection/index.html @@ -0,0 +1,13 @@ + + + + + + Svelte Row Selection Example + + + +
+ + + diff --git a/examples/svelte/row-selection/package.json b/examples/svelte/row-selection/package.json new file mode 100644 index 0000000000..ad84d5a9bc --- /dev/null +++ b/examples/svelte/row-selection/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-svelte-table-example-row-selection", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" + }, + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/svelte/row-selection/src/App.svelte b/examples/svelte/row-selection/src/App.svelte new file mode 100644 index 0000000000..a1078162f3 --- /dev/null +++ b/examples/svelte/row-selection/src/App.svelte @@ -0,0 +1,339 @@ + + +
+
+ + +
+
+ table.setGlobalFilter((e.target as HTMLInputElement).value)} + class="summary-panel" + placeholder="Search all columns..." + /> +
+
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id) + } + + {#each headerGroup.headers as header (header.id)} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getAllCells() as cell (cell.id)} + + {/each} + + {/each} + + + + + + + +
+ {#if !header.isPlaceholder} + {#if header.id === 'select'} + + {:else} + + {/if} + {#if header.column.getCanFilter()} +
+ {@render Filter(header.column, table)} +
+ {/if} + {/if} +
+ {#if cell.column.id === 'select'} + + {:else} + + {/if} +
+ + Page Rows ({table.getRowModel().rows.length.toLocaleString()})
+
+
+ + + + + +
Page
+ + {(table.state.pagination.pageIndex + 1).toLocaleString()} of {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = (e.target as HTMLInputElement).value ? Number((e.target as HTMLInputElement).value) - 1 : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
+
+
+ {Object.keys(table.state.rowSelection).length.toLocaleString()} of{' '} + {table.getPreFilteredRowModel().rows.length.toLocaleString()} Total Rows Selected +
+
+
+
+ + +
+
+ +
+
+ Row Selection State: +
{JSON.stringify(table.state, null, 2)
+    }
+
+
+ +{#snippet Filter( + column: Column, + table: SvelteTable, +)} + {@const firstValue = table + .getPreFilteredRowModel() + .flatRows[0]?.getValue(column.id)} + + {#if typeof firstValue === 'number'} +
+ + column.setFilterValue((old: any) => [(e.target as HTMLInputElement).value, old?.[1]]) + } + placeholder={`Min`} + class="filter-input" + /> + + column.setFilterValue((old: any) => [old?.[0], (e.target as HTMLInputElement).value]) + } + placeholder={`Max`} + class="filter-input" + /> +
+ {:else} + column.setFilterValue((e.target as HTMLInputElement).value)} + placeholder={`Search...`} + class="filter-select" + /> + {/if} +{/snippet} diff --git a/examples/svelte/row-selection/src/index.css b/examples/svelte/row-selection/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/svelte/row-selection/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/row-selection/src/main.ts b/examples/svelte/row-selection/src/main.ts new file mode 100644 index 0000000000..aac57e409c --- /dev/null +++ b/examples/svelte/row-selection/src/main.ts @@ -0,0 +1,9 @@ +// @ts-ignore -- svelte module types +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('root')!, +}) + +export default app diff --git a/examples/svelte/row-selection/src/makeData.ts b/examples/svelte/row-selection/src/makeData.ts new file mode 100644 index 0000000000..c34c43a03e --- /dev/null +++ b/examples/svelte/row-selection/src/makeData.ts @@ -0,0 +1,50 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + id: string + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + id: faker.string.uuid(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/svelte/row-selection/src/vite-env.d.ts b/examples/svelte/row-selection/src/vite-env.d.ts new file mode 100644 index 0000000000..66addc6b05 --- /dev/null +++ b/examples/svelte/row-selection/src/vite-env.d.ts @@ -0,0 +1,4 @@ +/// +/// + +declare module '*.css' {} diff --git a/examples/svelte/row-selection/svelte.config.js b/examples/svelte/row-selection/svelte.config.js new file mode 100644 index 0000000000..8abe4369b8 --- /dev/null +++ b/examples/svelte/row-selection/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/row-selection/tsconfig.json b/examples/svelte/row-selection/tsconfig.json new file mode 100644 index 0000000000..6761f6aaa4 --- /dev/null +++ b/examples/svelte/row-selection/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/svelte/row-selection/vite.config.js b/examples/svelte/row-selection/vite.config.js new file mode 100644 index 0000000000..c6ced40a24 --- /dev/null +++ b/examples/svelte/row-selection/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + svelte(), + ], +}) diff --git a/examples/svelte/sorting/index.html b/examples/svelte/sorting/index.html index 6ab1dd7e51..8441238e0e 100644 --- a/examples/svelte/sorting/index.html +++ b/examples/svelte/sorting/index.html @@ -4,7 +4,6 @@ Vite App - diff --git a/examples/svelte/sorting/package.json b/examples/svelte/sorting/package.json index 7bc312f3ce..8de4dcc1d1 100644 --- a/examples/svelte/sorting/package.json +++ b/examples/svelte/sorting/package.json @@ -1,23 +1,23 @@ { - "name": "tanstack-table-example-svelte-sorting", - "version": "0.0.0", + "name": "tanstack-svelte-table-example-sorting", "private": true, "type": "module", "scripts": { "dev": "vite", "build": "vite build", "serve": "vite preview", - "test:types": "svelte-check --tsconfig ./tsconfig.json" + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" }, "devDependencies": { - "@faker-js/faker": "^8.4.1", - "@rollup/plugin-replace": "^5.0.7", - "@sveltejs/vite-plugin-svelte": "^3.1.1", - "@tanstack/svelte-table": "^8.20.5", - "@tsconfig/svelte": "^5.0.4", - "svelte": "^4.2.18", - "svelte-check": "^3.8.4", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" } } diff --git a/examples/svelte/sorting/src/App.svelte b/examples/svelte/sorting/src/App.svelte index c48ea9df32..25d452eb9d 100644 --- a/examples/svelte/sorting/src/App.svelte +++ b/examples/svelte/sorting/src/App.svelte @@ -1,65 +1,73 @@ -
-
+
+
+ + +
+
- {#each $table.getHeaderGroups() as headerGroup} + {#each table.getHeaderGroups() as headerGroup (headerGroup.id) + } - {#each headerGroup.headers as header} + {#each headerGroup.headers as header (header.id)} {/each} @@ -149,30 +117,23 @@ {/each} - {#each $table.getRowModel().rows.slice(0, 10) as row} + {#each table.getRowModel().rows.slice(0, 10) as row (row.id)} - {#each row.getVisibleCells() as cell} + {#each row.getAllCells() as cell (cell.id)} {/each} {/each} - {#each $table.getFooterGroups() as footerGroup} + {#each table.getFooterGroups() as footerGroup (footerGroup.id)} - {#each footerGroup.headers as header} + {#each footerGroup.headers as header (header.id)} {/each} @@ -180,12 +141,11 @@ {/each}
{#if !header.isPlaceholder} -
- - {#if header.column.getIsSorted().toString() === 'asc'} - 🔼 - {:else if header.column.getIsSorted().toString() === 'desc'} - 🔽 - {/if} -
+ {/if}
- +
{#if !header.isPlaceholder} - + {/if}
-
{$table.getRowModel().rows.length} Rows
-
- -
+
{table.getRowModel().rows.length.toLocaleString() + } Rows
- + +
-
{JSON.stringify($table.getState().sorting, null, 2)}
+
{JSON.stringify(table.state, null, 2)}
diff --git a/examples/svelte/sorting/src/Header.svelte b/examples/svelte/sorting/src/Header.svelte new file mode 100644 index 0000000000..ba70c2c941 --- /dev/null +++ b/examples/svelte/sorting/src/Header.svelte @@ -0,0 +1,35 @@ + + +{#if header && header.column} + +{:else} + {label ?? ''} +{/if} + + diff --git a/examples/svelte/sorting/src/index.css b/examples/svelte/sorting/src/index.css index 43c09e0f6b..b9af6ded32 100644 --- a/examples/svelte/sorting/src/index.css +++ b/examples/svelte/sorting/src/index.css @@ -5,6 +5,8 @@ html { table { border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; } tbody { @@ -24,3 +26,340 @@ tfoot { tfoot th { font-weight: normal; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/sorting/src/main.ts b/examples/svelte/sorting/src/main.ts index 4a5606b9a2..aac57e409c 100644 --- a/examples/svelte/sorting/src/main.ts +++ b/examples/svelte/sorting/src/main.ts @@ -1,7 +1,8 @@ -// @ts-ignore +// @ts-ignore -- svelte module types +import { mount } from 'svelte' import App from './App.svelte' -const app = new App({ +const app = mount(App, { target: document.getElementById('root')!, }) diff --git a/examples/svelte/sorting/src/makeData.ts b/examples/svelte/sorting/src/makeData.ts index 331dd1eb19..b9fb014aba 100644 --- a/examples/svelte/sorting/src/makeData.ts +++ b/examples/svelte/sorting/src/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,13 +29,13 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] return range(len).map((d): Person => { return { ...newPerson(), diff --git a/examples/svelte/sorting/src/tableHelper.svelte.ts b/examples/svelte/sorting/src/tableHelper.svelte.ts new file mode 100644 index 0000000000..c319d35982 --- /dev/null +++ b/examples/svelte/sorting/src/tableHelper.svelte.ts @@ -0,0 +1,5 @@ +import { rowSortingFeature, tableFeatures } from '@tanstack/svelte-table' + +export const _features = tableFeatures({ + rowSortingFeature, +}) diff --git a/examples/svelte/sorting/src/vite-env.d.ts b/examples/svelte/sorting/src/vite-env.d.ts index 4078e7476a..66addc6b05 100644 --- a/examples/svelte/sorting/src/vite-env.d.ts +++ b/examples/svelte/sorting/src/vite-env.d.ts @@ -1,2 +1,4 @@ /// /// + +declare module '*.css' {} diff --git a/examples/svelte/sorting/tsconfig.json b/examples/svelte/sorting/tsconfig.json index e44d928411..6761f6aaa4 100644 --- a/examples/svelte/sorting/tsconfig.json +++ b/examples/svelte/sorting/tsconfig.json @@ -9,5 +9,13 @@ "checkJs": true, "isolatedModules": true }, - "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"] + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] } diff --git a/examples/svelte/sub-components/.gitignore b/examples/svelte/sub-components/.gitignore new file mode 100644 index 0000000000..2de1736a62 --- /dev/null +++ b/examples/svelte/sub-components/.gitignore @@ -0,0 +1,8 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local + +src/**/*.d.ts +src/**/*.map diff --git a/examples/svelte/sub-components/index.html b/examples/svelte/sub-components/index.html new file mode 100644 index 0000000000..8441238e0e --- /dev/null +++ b/examples/svelte/sub-components/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/svelte/sub-components/package.json b/examples/svelte/sub-components/package.json new file mode 100644 index 0000000000..c4bd6f8008 --- /dev/null +++ b/examples/svelte/sub-components/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-svelte-table-example-sub-components", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" + }, + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/svelte/sub-components/src/App.svelte b/examples/svelte/sub-components/src/App.svelte new file mode 100644 index 0000000000..9be58b4f3f --- /dev/null +++ b/examples/svelte/sub-components/src/App.svelte @@ -0,0 +1,152 @@ + + +{#snippet ExpanderButton(row: Row)} + {#if row.getCanExpand()} + + {:else} + * + {/if} +{/snippet} + +{#snippet SubComponent(row: Row)} +
+    {JSON.stringify(row.original, null, 2)}
+  
+{/snippet} + +
+
+ + +
+
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id) + } + + {#each headerGroup.headers as header (header.id)} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getAllCells() as cell (cell.id)} + + {/each} + + {#if row.getIsExpanded()} + + + + {/if} + {/each} + +
+ {#if !header.isPlaceholder} +
+ +
+ {/if} +
+ {#if cell.column.id === 'expander'} + {@render ExpanderButton(row)} + {:else if cell.column.id === 'firstName'} +
+ +
+ {:else} + + {/if} +
+ {@render SubComponent(row)} +
+
+
{table.getRowModel().rows.length.toLocaleString() + } Rows
+
diff --git a/examples/svelte/sub-components/src/index.css b/examples/svelte/sub-components/src/index.css new file mode 100644 index 0000000000..40a5eb73a4 --- /dev/null +++ b/examples/svelte/sub-components/src/index.css @@ -0,0 +1,360 @@ +html { + font-family: sans-serif; + font-size: 14px; +} +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} +tbody { + border-bottom: 1px solid lightgray; +} +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} +tfoot { + color: gray; +} +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/sub-components/src/main.ts b/examples/svelte/sub-components/src/main.ts new file mode 100644 index 0000000000..aac57e409c --- /dev/null +++ b/examples/svelte/sub-components/src/main.ts @@ -0,0 +1,9 @@ +// @ts-ignore -- svelte module types +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('root')!, +}) + +export default app diff --git a/examples/svelte/sub-components/src/makeData.ts b/examples/svelte/sub-components/src/makeData.ts new file mode 100644 index 0000000000..d63c724b74 --- /dev/null +++ b/examples/svelte/sub-components/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/svelte/sub-components/svelte.config.js b/examples/svelte/sub-components/svelte.config.js new file mode 100644 index 0000000000..8abe4369b8 --- /dev/null +++ b/examples/svelte/sub-components/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/sub-components/tsconfig.json b/examples/svelte/sub-components/tsconfig.json new file mode 100644 index 0000000000..6761f6aaa4 --- /dev/null +++ b/examples/svelte/sub-components/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/svelte/sub-components/vite.config.js b/examples/svelte/sub-components/vite.config.js new file mode 100644 index 0000000000..c6ced40a24 --- /dev/null +++ b/examples/svelte/sub-components/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + svelte(), + ], +}) diff --git a/examples/svelte/virtualized-columns/index.html b/examples/svelte/virtualized-columns/index.html new file mode 100644 index 0000000000..392fecf932 --- /dev/null +++ b/examples/svelte/virtualized-columns/index.html @@ -0,0 +1,13 @@ + + + + + + Svelte Table - Virtualized Columns + + + +
+ + + diff --git a/examples/svelte/virtualized-columns/package.json b/examples/svelte/virtualized-columns/package.json new file mode 100644 index 0000000000..b3d3f84849 --- /dev/null +++ b/examples/svelte/virtualized-columns/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-svelte-table-example-virtualized-columns", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" + }, + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tanstack/svelte-virtual": "^3.13.24", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/svelte/virtualized-columns/src/App.svelte b/examples/svelte/virtualized-columns/src/App.svelte new file mode 100644 index 0000000000..0f2cbe28aa --- /dev/null +++ b/examples/svelte/virtualized-columns/src/App.svelte @@ -0,0 +1,201 @@ + + +
+
+ + +
+
({columns.length.toLocaleString()} columns)
+
({data.length.toLocaleString()} rows)
+
+ + + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id) + } + + {#if virtualPaddingLeft} + + + {/if} + {#each $columnVirtualizer.getVirtualItems() as virtualColumn (virtualColumn.index)} + {@const header = headerGroup.headers[virtualColumn.index]} + + {/each} + {#if virtualPaddingRight} + + + {/if} + + {/each} + + + {#each $rowVirtualizer.getVirtualItems() as virtualRow (virtualRow.index)} + {@const row = rows[virtualRow.index]} + {@const visibleCells = row.getVisibleCells()} + + {#if virtualPaddingLeft} + + + {/if} + {#each $columnVirtualizer.getVirtualItems() as virtualColumn (virtualColumn.index)} + {@const cell = visibleCells[virtualColumn.index]} + + {/each} + {#if virtualPaddingRight} + + + {/if} + + {/each} + +
+
{ + if (e.key === 'Enter' || e.key === ' ') { + header.column.getToggleSortingHandler()?.(e) + } + }} + > + + {#if header.column.getIsSorted() === 'asc'} + {' '}🔼 + {:else if header.column.getIsSorted() === 'desc'} + {' '}🔽 + {/if} +
+
+ +
+
+
diff --git a/examples/svelte/virtualized-columns/src/index.css b/examples/svelte/virtualized-columns/src/index.css new file mode 100644 index 0000000000..720064ab9d --- /dev/null +++ b/examples/svelte/virtualized-columns/src/index.css @@ -0,0 +1,377 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border-collapse: collapse; + border-spacing: 0; + font-family: arial, sans-serif; + table-layout: fixed; +} + +thead { + background: lightgray; +} + +tr { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; + text-align: left; +} + +td { + padding: 6px; +} + +.container { + border: 1px solid lightgray; + margin: 1rem auto; +} + +.app { + margin: 1rem auto; + text-align: center; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/virtualized-columns/src/main.ts b/examples/svelte/virtualized-columns/src/main.ts new file mode 100644 index 0000000000..aac57e409c --- /dev/null +++ b/examples/svelte/virtualized-columns/src/main.ts @@ -0,0 +1,9 @@ +// @ts-ignore -- svelte module types +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('root')!, +}) + +export default app diff --git a/examples/svelte/virtualized-columns/src/makeData.ts b/examples/svelte/virtualized-columns/src/makeData.ts new file mode 100644 index 0000000000..837114e211 --- /dev/null +++ b/examples/svelte/virtualized-columns/src/makeData.ts @@ -0,0 +1,17 @@ +import { faker } from '@faker-js/faker' + +export const makeColumns = (num: number) => + [...Array(num)].map((_, i) => ({ + accessorKey: i.toString(), + header: 'Column ' + i.toString(), + size: Math.floor(Math.random() * 150) + 100, + })) + +export const makeData = (num: number, columns: Array) => + [...Array(num)].map(() => ({ + ...Object.fromEntries( + columns.map((col: any) => [col.accessorKey, faker.person.firstName()]), + ), + })) + +export type Person = ReturnType[0] diff --git a/examples/svelte/virtualized-columns/src/vite-env.d.ts b/examples/svelte/virtualized-columns/src/vite-env.d.ts new file mode 100644 index 0000000000..66addc6b05 --- /dev/null +++ b/examples/svelte/virtualized-columns/src/vite-env.d.ts @@ -0,0 +1,4 @@ +/// +/// + +declare module '*.css' {} diff --git a/examples/svelte/virtualized-columns/svelte.config.js b/examples/svelte/virtualized-columns/svelte.config.js new file mode 100644 index 0000000000..8abe4369b8 --- /dev/null +++ b/examples/svelte/virtualized-columns/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/virtualized-columns/tsconfig.json b/examples/svelte/virtualized-columns/tsconfig.json new file mode 100644 index 0000000000..6761f6aaa4 --- /dev/null +++ b/examples/svelte/virtualized-columns/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/svelte/virtualized-columns/vite.config.js b/examples/svelte/virtualized-columns/vite.config.js new file mode 100644 index 0000000000..c6ced40a24 --- /dev/null +++ b/examples/svelte/virtualized-columns/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + svelte(), + ], +}) diff --git a/examples/svelte/virtualized-infinite-scrolling/index.html b/examples/svelte/virtualized-infinite-scrolling/index.html new file mode 100644 index 0000000000..a32d7d57d9 --- /dev/null +++ b/examples/svelte/virtualized-infinite-scrolling/index.html @@ -0,0 +1,13 @@ + + + + + + Svelte Table - Virtualized Infinite Scrolling + + + +
+ + + diff --git a/examples/svelte/virtualized-infinite-scrolling/package.json b/examples/svelte/virtualized-infinite-scrolling/package.json new file mode 100644 index 0000000000..1285d5dd07 --- /dev/null +++ b/examples/svelte/virtualized-infinite-scrolling/package.json @@ -0,0 +1,25 @@ +{ + "name": "tanstack-svelte-table-example-virtualized-infinite-scrolling", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" + }, + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-query": "^6.1.28", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tanstack/svelte-virtual": "^3.13.24", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/svelte/virtualized-infinite-scrolling/src/App.svelte b/examples/svelte/virtualized-infinite-scrolling/src/App.svelte new file mode 100644 index 0000000000..808da07998 --- /dev/null +++ b/examples/svelte/virtualized-infinite-scrolling/src/App.svelte @@ -0,0 +1,245 @@ + + +
+ {#if import.meta.env.DEV} +

+ Notice: You are currently running Svelte in development mode. + Virtualized rendering performance will be slightly degraded until this + application is built for production. +

+ {/if} + ({totalFetched.toLocaleString()} of {totalDBRowCount.toLocaleString()} rows fetched) +
fetchMoreOnBottomReached(e.currentTarget as HTMLDivElement)} + bind:this={tableContainerRef} + style="overflow: auto; position: relative; height: 600px;" + > + + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id) + } + + {#each headerGroup.headers as header (header.id)} + + {/each} + + {/each} + + + {#each $rowVirtualizer.getVirtualItems() as virtualRow (virtualRow.index)} + {@const row = rows[virtualRow.index]} + + {#each row.getAllCells() as cell (cell.id)} + + {/each} + + {/each} + +
+
{ + if (e.key === 'Enter' || e.key === ' ') { + header.column.getToggleSortingHandler()?.(e) + } + }} + > + + {#if header.column.getIsSorted() === 'asc'} + {' '}🔼 + {:else if header.column.getIsSorted() === 'desc'} + {' '}🔽 + {/if} +
+
+ +
+
+ {#if query.isFetching + } +
Fetching More...
+ {/if} +
diff --git a/examples/svelte/virtualized-infinite-scrolling/src/Root.svelte b/examples/svelte/virtualized-infinite-scrolling/src/Root.svelte new file mode 100644 index 0000000000..cda9d6aac9 --- /dev/null +++ b/examples/svelte/virtualized-infinite-scrolling/src/Root.svelte @@ -0,0 +1,10 @@ + + + + + diff --git a/examples/svelte/virtualized-infinite-scrolling/src/index.css b/examples/svelte/virtualized-infinite-scrolling/src/index.css new file mode 100644 index 0000000000..720064ab9d --- /dev/null +++ b/examples/svelte/virtualized-infinite-scrolling/src/index.css @@ -0,0 +1,377 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border-collapse: collapse; + border-spacing: 0; + font-family: arial, sans-serif; + table-layout: fixed; +} + +thead { + background: lightgray; +} + +tr { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; + text-align: left; +} + +td { + padding: 6px; +} + +.container { + border: 1px solid lightgray; + margin: 1rem auto; +} + +.app { + margin: 1rem auto; + text-align: center; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/virtualized-infinite-scrolling/src/main.ts b/examples/svelte/virtualized-infinite-scrolling/src/main.ts new file mode 100644 index 0000000000..de3a6bd4b0 --- /dev/null +++ b/examples/svelte/virtualized-infinite-scrolling/src/main.ts @@ -0,0 +1,9 @@ +// @ts-ignore -- svelte module types +import { mount } from 'svelte' +import Root from './Root.svelte' + +const app = mount(Root, { + target: document.getElementById('root')!, +}) + +export default app diff --git a/examples/svelte/virtualized-infinite-scrolling/src/makeData.ts b/examples/svelte/virtualized-infinite-scrolling/src/makeData.ts new file mode 100644 index 0000000000..f482393d88 --- /dev/null +++ b/examples/svelte/virtualized-infinite-scrolling/src/makeData.ts @@ -0,0 +1,89 @@ +import { faker } from '@faker-js/faker' +import type { SortingState } from '@tanstack/svelte-table' + +export type Person = { + id: number + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + createdAt: Date +} + +export type PersonApiResponse = { + data: Array + meta: { + totalRowCount: number + } +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (index: number): Person => { + return { + id: index + 1, + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + createdAt: faker.date.anytime(), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((d): Person => { + return { + ...newPerson(d), + } + }) + } + + return makeDataLevel() +} + +const data = makeData(1000) + +// simulates a backend api +export const fetchData = async ( + start: number, + size: number, + sorting: SortingState, +) => { + const dbData = [...data] + if (sorting.length) { + const sort = sorting[0] + const { id, desc } = sort as { id: keyof Person; desc: boolean } + dbData.sort((a, b) => { + if (desc) { + return a[id] < b[id] ? 1 : -1 + } + return a[id] > b[id] ? 1 : -1 + }) + } + + // simulate a backend api + await new Promise((resolve) => setTimeout(resolve, 200)) + + return { + data: dbData.slice(start, start + size), + meta: { + totalRowCount: dbData.length, + }, + } +} diff --git a/examples/svelte/virtualized-infinite-scrolling/src/vite-env.d.ts b/examples/svelte/virtualized-infinite-scrolling/src/vite-env.d.ts new file mode 100644 index 0000000000..66addc6b05 --- /dev/null +++ b/examples/svelte/virtualized-infinite-scrolling/src/vite-env.d.ts @@ -0,0 +1,4 @@ +/// +/// + +declare module '*.css' {} diff --git a/examples/svelte/virtualized-infinite-scrolling/svelte.config.js b/examples/svelte/virtualized-infinite-scrolling/svelte.config.js new file mode 100644 index 0000000000..8abe4369b8 --- /dev/null +++ b/examples/svelte/virtualized-infinite-scrolling/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/virtualized-infinite-scrolling/tsconfig.json b/examples/svelte/virtualized-infinite-scrolling/tsconfig.json new file mode 100644 index 0000000000..6761f6aaa4 --- /dev/null +++ b/examples/svelte/virtualized-infinite-scrolling/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/svelte/virtualized-infinite-scrolling/vite.config.js b/examples/svelte/virtualized-infinite-scrolling/vite.config.js new file mode 100644 index 0000000000..c6ced40a24 --- /dev/null +++ b/examples/svelte/virtualized-infinite-scrolling/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + svelte(), + ], +}) diff --git a/examples/svelte/virtualized-rows/index.html b/examples/svelte/virtualized-rows/index.html new file mode 100644 index 0000000000..f728c10d59 --- /dev/null +++ b/examples/svelte/virtualized-rows/index.html @@ -0,0 +1,13 @@ + + + + + + Svelte Table - Virtualized Rows + + + +
+ + + diff --git a/examples/svelte/virtualized-rows/package.json b/examples/svelte/virtualized-rows/package.json new file mode 100644 index 0000000000..8182a9dea3 --- /dev/null +++ b/examples/svelte/virtualized-rows/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-svelte-table-example-virtualized-rows", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" + }, + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "@tanstack/svelte-virtual": "^3.13.24", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" + } +} diff --git a/examples/svelte/virtualized-rows/src/App.svelte b/examples/svelte/virtualized-rows/src/App.svelte new file mode 100644 index 0000000000..27a8d3b058 --- /dev/null +++ b/examples/svelte/virtualized-rows/src/App.svelte @@ -0,0 +1,184 @@ + + +
+
+ + +
+ ({data.length.toLocaleString()} rows) +
+ + + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id) + } + + {#each headerGroup.headers as header (header.id)} + + {/each} + + {/each} + + + {#each $rowVirtualizer.getVirtualItems() as virtualRow (virtualRow.index)} + {@const row = rows[virtualRow.index]} + + {#each row.getAllCells() as cell (cell.id)} + + {/each} + + {/each} + +
+
{ + if (e.key === 'Enter' || e.key === ' ') { + header.column.getToggleSortingHandler()?.(e) + } + }} + > + + {#if header.column.getIsSorted() === 'asc'} + {' '}🔼 + {:else if header.column.getIsSorted() === 'desc'} + {' '}🔽 + {/if} +
+
+ +
+
+
diff --git a/examples/svelte/virtualized-rows/src/index.css b/examples/svelte/virtualized-rows/src/index.css new file mode 100644 index 0000000000..720064ab9d --- /dev/null +++ b/examples/svelte/virtualized-rows/src/index.css @@ -0,0 +1,377 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border-collapse: collapse; + border-spacing: 0; + font-family: arial, sans-serif; + table-layout: fixed; +} + +thead { + background: lightgray; +} + +tr { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; + text-align: left; +} + +td { + padding: 6px; +} + +.container { + border: 1px solid lightgray; + margin: 1rem auto; +} + +.app { + margin: 1rem auto; + text-align: center; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/virtualized-rows/src/main.ts b/examples/svelte/virtualized-rows/src/main.ts new file mode 100644 index 0000000000..aac57e409c --- /dev/null +++ b/examples/svelte/virtualized-rows/src/main.ts @@ -0,0 +1,9 @@ +// @ts-ignore -- svelte module types +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('root')!, +}) + +export default app diff --git a/examples/svelte/virtualized-rows/src/makeData.ts b/examples/svelte/virtualized-rows/src/makeData.ts new file mode 100644 index 0000000000..acd0e474d9 --- /dev/null +++ b/examples/svelte/virtualized-rows/src/makeData.ts @@ -0,0 +1,41 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + id: number + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + createdAt: Date +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (index: number): Person => ({ + id: index + 1, + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + createdAt: faker.date.anytime(), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((d): Person => ({ ...newPerson(d) })) + } + return makeDataLevel() +} diff --git a/examples/svelte/virtualized-rows/src/vite-env.d.ts b/examples/svelte/virtualized-rows/src/vite-env.d.ts new file mode 100644 index 0000000000..66addc6b05 --- /dev/null +++ b/examples/svelte/virtualized-rows/src/vite-env.d.ts @@ -0,0 +1,4 @@ +/// +/// + +declare module '*.css' {} diff --git a/examples/svelte/virtualized-rows/svelte.config.js b/examples/svelte/virtualized-rows/svelte.config.js new file mode 100644 index 0000000000..8abe4369b8 --- /dev/null +++ b/examples/svelte/virtualized-rows/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/virtualized-rows/tsconfig.json b/examples/svelte/virtualized-rows/tsconfig.json new file mode 100644 index 0000000000..6761f6aaa4 --- /dev/null +++ b/examples/svelte/virtualized-rows/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/svelte/virtualized-rows/vite.config.js b/examples/svelte/virtualized-rows/vite.config.js new file mode 100644 index 0000000000..c6ced40a24 --- /dev/null +++ b/examples/svelte/virtualized-rows/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + svelte(), + ], +}) diff --git a/examples/svelte/vite-env.d.ts b/examples/svelte/vite-env.d.ts new file mode 100644 index 0000000000..66addc6b05 --- /dev/null +++ b/examples/svelte/vite-env.d.ts @@ -0,0 +1,4 @@ +/// +/// + +declare module '*.css' {} diff --git a/examples/svelte/with-tanstack-form/.gitignore b/examples/svelte/with-tanstack-form/.gitignore new file mode 100644 index 0000000000..2de1736a62 --- /dev/null +++ b/examples/svelte/with-tanstack-form/.gitignore @@ -0,0 +1,8 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local + +src/**/*.d.ts +src/**/*.map diff --git a/examples/svelte/with-tanstack-form/index.html b/examples/svelte/with-tanstack-form/index.html new file mode 100644 index 0000000000..8441238e0e --- /dev/null +++ b/examples/svelte/with-tanstack-form/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/svelte/with-tanstack-form/package.json b/examples/svelte/with-tanstack-form/package.json new file mode 100644 index 0000000000..0ba9e83397 --- /dev/null +++ b/examples/svelte/with-tanstack-form/package.json @@ -0,0 +1,28 @@ +{ + "name": "tanstack-svelte-table-example-with-tanstack-form", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" + }, + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" + }, + "dependencies": { + "@tanstack/form-core": "^1.31.0", + "@tanstack/svelte-form": "^1.31.0", + "@tanstack/svelte-table": "^9.0.0-alpha.45", + "zod": "^4.4.3" + } +} diff --git a/examples/svelte/with-tanstack-form/src/App.svelte b/examples/svelte/with-tanstack-form/src/App.svelte new file mode 100644 index 0000000000..5ce1e8284d --- /dev/null +++ b/examples/svelte/with-tanstack-form/src/App.svelte @@ -0,0 +1,376 @@ + + +{#snippet filterSnippet(column: Column)} + {@const firstValue = getFirstValue(table, column.id)} + {@const filterValue = getFilterValue(column)} + {#if typeof firstValue === 'number'} +
+ + column.setFilterValue((old: [number, number]) => [ + (e.target as HTMLInputElement).value, + old?.[1], + ])} + placeholder="Min" + class="filter-input" + /> + + column.setFilterValue((old: [number, number]) => [ + old?.[0], + (e.target as HTMLInputElement).value, + ])} + placeholder="Max" + class="filter-input" + /> +
+ {:else} + + column.setFilterValue((e.target as HTMLInputElement).value)} + placeholder="Search..." + type="text" + value={(filterValue ?? '') as string} + /> + {/if} +{/snippet} + +
+
+ + +
+
{ + e.preventDefault() + e.stopPropagation() + void form.handleSubmit() + }} + > + +
+ + {#snippet children()} + + {/snippet} + + + {#snippet children()} + + {/snippet} + + + +
+ + +
+
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id) + } + + {#each headerGroup.headers as header (header.id)} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getAllCells() as cell (cell.id)} + + {/each} + + {/each} + +
+ {#if !header.isPlaceholder} +
+ + {#if header.column.getCanFilter()} +
+ {@render filterSnippet(header.column)} +
+ {/if} +
+ {/if} +
+ +
+ + +
+
+ + + + + +
Page
+ + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = (e.target as HTMLInputElement).value + ? Number((e.target as HTMLInputElement).value) - 1 + : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
+
+ Showing {table.getRowModel().rows.length.toLocaleString()} of{' '} + {table.getRowCount().toLocaleString()} Rows +
+
+
+
diff --git a/examples/svelte/with-tanstack-form/src/FormStateIndicator.svelte b/examples/svelte/with-tanstack-form/src/FormStateIndicator.svelte new file mode 100644 index 0000000000..41299d24db --- /dev/null +++ b/examples/svelte/with-tanstack-form/src/FormStateIndicator.svelte @@ -0,0 +1,29 @@ + + + ({ + isDirty: state.isDirty, + isValid: state.isValid, + errorMap: state.errorMap, + })} +> + {#snippet children({ isDirty, isValid, errorMap })} +
+ + {isDirty ? '● Modified' : '○ Pristine'} + + + {isValid ? '✓ Valid' : '✗ Invalid'} + + {#if Object.keys(errorMap).length > 0} + + Errors: {JSON.stringify(errorMap)} + + {/if} +
+ {/snippet} +
diff --git a/examples/svelte/with-tanstack-form/src/NumberField.svelte b/examples/svelte/with-tanstack-form/src/NumberField.svelte new file mode 100644 index 0000000000..009d5d2f07 --- /dev/null +++ b/examples/svelte/with-tanstack-form/src/NumberField.svelte @@ -0,0 +1,19 @@ + + +
+ + field.handleChange(Number((e.target as HTMLInputElement).value))} + onblur={() => field.handleBlur()} + /> + {#if field.state.meta.errors.length > 0} +
{field.state.meta.errors.join(', ')}
+ {/if} +
diff --git a/examples/svelte/with-tanstack-form/src/NumberFieldCell.svelte b/examples/svelte/with-tanstack-form/src/NumberFieldCell.svelte new file mode 100644 index 0000000000..fda51276aa --- /dev/null +++ b/examples/svelte/with-tanstack-form/src/NumberFieldCell.svelte @@ -0,0 +1,29 @@ + + + + {#snippet children(field: any)} + + {/snippet} + diff --git a/examples/svelte/with-tanstack-form/src/SelectField.svelte b/examples/svelte/with-tanstack-form/src/SelectField.svelte new file mode 100644 index 0000000000..8919deccac --- /dev/null +++ b/examples/svelte/with-tanstack-form/src/SelectField.svelte @@ -0,0 +1,24 @@ + + +
+ + {#if field.state.meta.errors.length > 0} +
{field.state.meta.errors.join(', ')}
+ {/if} +
diff --git a/examples/svelte/with-tanstack-form/src/SelectFieldCell.svelte b/examples/svelte/with-tanstack-form/src/SelectFieldCell.svelte new file mode 100644 index 0000000000..6d3192dafa --- /dev/null +++ b/examples/svelte/with-tanstack-form/src/SelectFieldCell.svelte @@ -0,0 +1,14 @@ + + + + {#snippet children(field: any)} + + {/snippet} + diff --git a/examples/svelte/with-tanstack-form/src/SubmitButton.svelte b/examples/svelte/with-tanstack-form/src/SubmitButton.svelte new file mode 100644 index 0000000000..104f47c5cc --- /dev/null +++ b/examples/svelte/with-tanstack-form/src/SubmitButton.svelte @@ -0,0 +1,24 @@ + + + ({ + isSubmitting: state.isSubmitting, + canSubmit: state.canSubmit, + })} +> + {#snippet children({ isSubmitting, canSubmit })} + + {/snippet} + diff --git a/examples/svelte/with-tanstack-form/src/TextField.svelte b/examples/svelte/with-tanstack-form/src/TextField.svelte new file mode 100644 index 0000000000..e6c439be55 --- /dev/null +++ b/examples/svelte/with-tanstack-form/src/TextField.svelte @@ -0,0 +1,18 @@ + + +
+ + field.handleChange((e.target as HTMLInputElement).value)} + onblur={() => field.handleBlur()} + /> + {#if field.state.meta.errors.length > 0} +
{field.state.meta.errors.join(', ')}
+ {/if} +
diff --git a/examples/svelte/with-tanstack-form/src/TextFieldCell.svelte b/examples/svelte/with-tanstack-form/src/TextFieldCell.svelte new file mode 100644 index 0000000000..fe28f99e79 --- /dev/null +++ b/examples/svelte/with-tanstack-form/src/TextFieldCell.svelte @@ -0,0 +1,21 @@ + + + + {#snippet children(field: any)} + + {/snippet} + diff --git a/examples/svelte/with-tanstack-form/src/form-context.ts b/examples/svelte/with-tanstack-form/src/form-context.ts new file mode 100644 index 0000000000..40989db7f5 --- /dev/null +++ b/examples/svelte/with-tanstack-form/src/form-context.ts @@ -0,0 +1,3 @@ +import { createFormCreatorContexts } from '@tanstack/svelte-form' + +export const { useFieldContext, useFormContext } = createFormCreatorContexts() diff --git a/examples/svelte/with-tanstack-form/src/form.ts b/examples/svelte/with-tanstack-form/src/form.ts new file mode 100644 index 0000000000..c796c870e1 --- /dev/null +++ b/examples/svelte/with-tanstack-form/src/form.ts @@ -0,0 +1,18 @@ +import { createFormCreator } from '@tanstack/svelte-form' +import TextField from './TextField.svelte' +import NumberField from './NumberField.svelte' +import SelectField from './SelectField.svelte' +import SubmitButton from './SubmitButton.svelte' +import FormStateIndicator from './FormStateIndicator.svelte' + +export const { createAppForm } = createFormCreator({ + fieldComponents: { + TextField, + NumberField, + SelectField, + }, + formComponents: { + SubmitButton, + FormStateIndicator, + }, +}) diff --git a/examples/svelte/with-tanstack-form/src/index.css b/examples/svelte/with-tanstack-form/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/svelte/with-tanstack-form/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/with-tanstack-form/src/main.ts b/examples/svelte/with-tanstack-form/src/main.ts new file mode 100644 index 0000000000..aac57e409c --- /dev/null +++ b/examples/svelte/with-tanstack-form/src/main.ts @@ -0,0 +1,9 @@ +// @ts-ignore -- svelte module types +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('root')!, +}) + +export default app diff --git a/examples/svelte/with-tanstack-form/src/makeData.ts b/examples/svelte/with-tanstack-form/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/svelte/with-tanstack-form/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/svelte/with-tanstack-form/svelte.config.js b/examples/svelte/with-tanstack-form/svelte.config.js new file mode 100644 index 0000000000..8abe4369b8 --- /dev/null +++ b/examples/svelte/with-tanstack-form/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/with-tanstack-form/tsconfig.json b/examples/svelte/with-tanstack-form/tsconfig.json new file mode 100644 index 0000000000..6761f6aaa4 --- /dev/null +++ b/examples/svelte/with-tanstack-form/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/svelte/with-tanstack-form/vite.config.js b/examples/svelte/with-tanstack-form/vite.config.js new file mode 100644 index 0000000000..c6ced40a24 --- /dev/null +++ b/examples/svelte/with-tanstack-form/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + svelte(), + ], +}) diff --git a/examples/svelte/with-tanstack-query/.gitignore b/examples/svelte/with-tanstack-query/.gitignore new file mode 100644 index 0000000000..2de1736a62 --- /dev/null +++ b/examples/svelte/with-tanstack-query/.gitignore @@ -0,0 +1,8 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local + +src/**/*.d.ts +src/**/*.map diff --git a/examples/svelte/with-tanstack-query/index.html b/examples/svelte/with-tanstack-query/index.html new file mode 100644 index 0000000000..8441238e0e --- /dev/null +++ b/examples/svelte/with-tanstack-query/index.html @@ -0,0 +1,13 @@ + + + + + + Vite App + + + +
+ + + diff --git a/examples/svelte/with-tanstack-query/package.json b/examples/svelte/with-tanstack-query/package.json new file mode 100644 index 0000000000..face422f5c --- /dev/null +++ b/examples/svelte/with-tanstack-query/package.json @@ -0,0 +1,26 @@ +{ + "name": "tanstack-svelte-table-example-with-tanstack-query", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "test:types": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint ./src" + }, + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "6.0.3", + "vite": "^8.0.11" + }, + "dependencies": { + "@tanstack/svelte-query": "^6.1.28", + "@tanstack/svelte-table": "^9.0.0-alpha.45" + } +} diff --git a/examples/svelte/with-tanstack-query/src/App.svelte b/examples/svelte/with-tanstack-query/src/App.svelte new file mode 100644 index 0000000000..de751518ce --- /dev/null +++ b/examples/svelte/with-tanstack-query/src/App.svelte @@ -0,0 +1,187 @@ + + +
+
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id) + } + + {#each headerGroup.headers as header (header.id)} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getAllCells() as cell (cell.id)} + + {/each} + + {/each} + +
+ {#if !header.isPlaceholder} + + {/if} +
+ +
+
+
+ + + + + +
Page
+ + {(pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const page = (e.target as HTMLInputElement).value + ? Number((e.target as HTMLInputElement).value) - 1 + : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + + {#if dataQuery.isFetching} + Loading... + {/if} +
+
+ Showing {table.getRowModel().rows.length.toLocaleString()} of{' '} + {dataQuery.data?.rowCount?.toLocaleString() ?? 0} Rows +
+
{JSON.stringify(table.state, null, 2)}
+
diff --git a/examples/svelte/with-tanstack-query/src/Root.svelte b/examples/svelte/with-tanstack-query/src/Root.svelte new file mode 100644 index 0000000000..0f8018b132 --- /dev/null +++ b/examples/svelte/with-tanstack-query/src/Root.svelte @@ -0,0 +1,10 @@ + + + + + diff --git a/examples/svelte/with-tanstack-query/src/fetchData.ts b/examples/svelte/with-tanstack-query/src/fetchData.ts new file mode 100644 index 0000000000..0ad745b375 --- /dev/null +++ b/examples/svelte/with-tanstack-query/src/fetchData.ts @@ -0,0 +1,66 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} + +const data = makeData(10000) + +export async function fetchData(options: { + pageIndex: number + pageSize: number +}) { + await new Promise((resolve) => setTimeout(resolve, 500)) + + return { + rows: data.slice( + options.pageIndex * options.pageSize, + (options.pageIndex + 1) * options.pageSize, + ), + pageCount: Math.ceil(data.length / options.pageSize), + rowCount: data.length, + } +} diff --git a/examples/svelte/with-tanstack-query/src/index.css b/examples/svelte/with-tanstack-query/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/svelte/with-tanstack-query/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/svelte/with-tanstack-query/src/main.ts b/examples/svelte/with-tanstack-query/src/main.ts new file mode 100644 index 0000000000..de3a6bd4b0 --- /dev/null +++ b/examples/svelte/with-tanstack-query/src/main.ts @@ -0,0 +1,9 @@ +// @ts-ignore -- svelte module types +import { mount } from 'svelte' +import Root from './Root.svelte' + +const app = mount(Root, { + target: document.getElementById('root')!, +}) + +export default app diff --git a/examples/svelte/with-tanstack-query/svelte.config.js b/examples/svelte/with-tanstack-query/svelte.config.js new file mode 100644 index 0000000000..8abe4369b8 --- /dev/null +++ b/examples/svelte/with-tanstack-query/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/with-tanstack-query/tsconfig.json b/examples/svelte/with-tanstack-query/tsconfig.json new file mode 100644 index 0000000000..6761f6aaa4 --- /dev/null +++ b/examples/svelte/with-tanstack-query/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.svelte", + "../vite-env.d.ts", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/svelte/with-tanstack-query/vite.config.js b/examples/svelte/with-tanstack-query/vite.config.js new file mode 100644 index 0000000000..c6ced40a24 --- /dev/null +++ b/examples/svelte/with-tanstack-query/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + svelte(), + ], +}) diff --git a/examples/vanilla/basic/index.html b/examples/vanilla/basic/index.html index 1cff7ebfc1..808a0111e1 100644 --- a/examples/vanilla/basic/index.html +++ b/examples/vanilla/basic/index.html @@ -4,10 +4,9 @@ Vite + TS - -
+
diff --git a/examples/vanilla/basic/package.json b/examples/vanilla/basic/package.json index ef0befb447..80fc52aec7 100644 --- a/examples/vanilla/basic/package.json +++ b/examples/vanilla/basic/package.json @@ -1,6 +1,5 @@ { - "name": "tanstack-table-example-vanilla-basic", - "version": "0.0.0", + "name": "tanstack-vanilla-table-example-basic", "private": true, "scripts": { "dev": "vite", @@ -9,12 +8,13 @@ "start": "vite" }, "devDependencies": { - "@rollup/plugin-replace": "^5.0.7", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@types/node": "^25.6.2", + "typescript": "6.0.3", + "vite": "^8.0.11" }, "dependencies": { - "@tanstack/table-core": "^8.20.5", - "nanostores": "^0.11.3" + "@tanstack/table-core": "^9.0.0-alpha.43" } } diff --git a/examples/vanilla/basic/src/index.css b/examples/vanilla/basic/src/index.css index 43c09e0f6b..b9af6ded32 100644 --- a/examples/vanilla/basic/src/index.css +++ b/examples/vanilla/basic/src/index.css @@ -5,6 +5,8 @@ html { table { border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; } tbody { @@ -24,3 +26,340 @@ tfoot { tfoot th { font-weight: normal; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/vanilla/basic/src/main.ts b/examples/vanilla/basic/src/main.ts index a041ac7ab2..3c5164ca6e 100644 --- a/examples/vanilla/basic/src/main.ts +++ b/examples/vanilla/basic/src/main.ts @@ -1,78 +1,73 @@ import './index.css' +import { + constructTable, + createColumnHelper, + tableFeatures, +} from '@tanstack/table-core' +import { FlexRender } from '@tanstack/table-core/flex-render' +import { storeReactivityBindings } from '@tanstack/table-core/store-reactivity-bindings' +import { makeData } from './makeData' +import type { Person } from './makeData' -import { createColumnHelper, getCoreRowModel } from '@tanstack/table-core' +let data = makeData(20) -import { flexRender, useTable } from './useTable' +const _features = tableFeatures({ + coreReativityFeature: storeReactivityBindings(), +}) -type Person = { - firstName: string - lastName: string - age: number - visits: number - status: string - progress: number -} +const columnHelper = createColumnHelper() -const data: Person[] = [ - { - firstName: 'tanner', - lastName: 'linsley', - age: 24, - visits: 100, - status: 'In Relationship', - progress: 50, - }, - { - firstName: 'tandy', - lastName: 'miller', - age: 40, - visits: 40, - status: 'Single', - progress: 80, - }, - { - firstName: 'joe', - lastName: 'dirte', - age: 45, - visits: 20, - status: 'Complicated', - progress: 10, - }, -] - -const columnHelper = createColumnHelper() - -const columns = [ +const columns = columnHelper.columns([ columnHelper.accessor('firstName', { - cell: info => info.getValue(), - footer: info => info.column.id, + cell: (info) => info.getValue(), + footer: (info) => info.column.id, }), - columnHelper.accessor(row => row.lastName, { + columnHelper.accessor((row) => row.lastName, { id: 'lastName', - cell: info => `${info.getValue()}`, + cell: (info) => `${info.getValue()}`, header: () => 'Last Name', - footer: info => info.column.id, + footer: (info) => info.column.id, }), columnHelper.accessor('age', { header: () => 'Age', - cell: info => info.renderValue(), - footer: info => info.column.id, + cell: (info) => info.renderValue(), + footer: (info) => info.column.id, }), columnHelper.accessor('visits', { header: () => 'Visits', - footer: info => info.column.id, + footer: (info) => info.column.id, }), columnHelper.accessor('status', { header: 'Status', - footer: info => info.column.id, + footer: (info) => info.column.id, }), columnHelper.accessor('progress', { header: 'Profile Progress', - footer: info => info.column.id, + footer: (info) => info.column.id, }), -] +]) const renderTable = () => { + // Create buttons container + const buttonsDiv = document.createElement('div') + + const regenerateBtn = document.createElement('button') + regenerateBtn.textContent = 'Regenerate Data' + regenerateBtn.addEventListener('click', () => { + data = makeData(20) + table.setOptions((prev) => ({ ...prev, data })) + }) + + const stressTestBtn = document.createElement('button') + stressTestBtn.textContent = 'Stress Test (1k rows)' + stressTestBtn.addEventListener('click', () => { + data = makeData(1_000) + table.setOptions((prev) => ({ ...prev, data })) + }) + + buttonsDiv.appendChild(regenerateBtn) + buttonsDiv.appendChild(stressTestBtn) + // Create table elements const tableElement = document.createElement('table') const theadElement = document.createElement('thead') @@ -84,40 +79,37 @@ const renderTable = () => { tableElement.appendChild(tfootElement) // Render table headers - table.getHeaderGroups().forEach(headerGroup => { + table.getHeaderGroups().forEach((headerGroup) => { const trElement = document.createElement('tr') - headerGroup.headers.forEach(header => { + headerGroup.headers.forEach((header) => { const thElement = document.createElement('th') thElement.innerHTML = header.isPlaceholder ? '' - : flexRender(header.column.columnDef.header, header.getContext()) + : String(FlexRender({ header }) ?? '') trElement.appendChild(thElement) }) theadElement.appendChild(trElement) }) // Render table rows - table.getRowModel().rows.forEach(row => { + table.getRowModel().rows.forEach((row) => { const trElement = document.createElement('tr') - row.getVisibleCells().forEach(cell => { + row.getAllCells().forEach((cell) => { const tdElement = document.createElement('td') - tdElement.innerHTML = flexRender( - cell.column.columnDef.cell, - cell.getContext() - ) + tdElement.innerHTML = String(FlexRender({ cell }) ?? '') trElement.appendChild(tdElement) }) tbodyElement.appendChild(trElement) }) // Render table footers - table.getFooterGroups().forEach(footerGroup => { + table.getFooterGroups().forEach((footerGroup) => { const trElement = document.createElement('tr') - footerGroup.headers.forEach(header => { + footerGroup.headers.forEach((header) => { const thElement = document.createElement('th') thElement.innerHTML = header.isPlaceholder ? '' - : flexRender(header.column.columnDef.footer, header.getContext()) + : String(FlexRender({ footer: header }) ?? '') trElement.appendChild(thElement) }) tfootElement.appendChild(trElement) @@ -126,13 +118,19 @@ const renderTable = () => { // Clear previous content and append new content const wrapperElement = document.getElementById('wrapper') as HTMLDivElement wrapperElement.innerHTML = '' + wrapperElement.appendChild(buttonsDiv) wrapperElement.appendChild(tableElement) } -const table = useTable({ - data, +const table = constructTable({ + debugTable: true, + _features, + _rowModels: {}, columns, - getCoreRowModel: getCoreRowModel(), + data, + debugAll: true, }) +table.store.subscribe(() => renderTable()) + renderTable() diff --git a/examples/vanilla/basic/src/makeData.ts b/examples/vanilla/basic/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/vanilla/basic/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/vanilla/basic/src/useTable.ts b/examples/vanilla/basic/src/useTable.ts deleted file mode 100644 index df476ee682..0000000000 --- a/examples/vanilla/basic/src/useTable.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { atom } from 'nanostores' - -import { - type RowData, - type TableOptions, - type TableOptionsResolved, - createTable, -} from '@tanstack/table-core' - -export const flexRender = (comp: any, props: TProps) => { - if (typeof comp === 'function') { - return comp(props) - } - return comp -} - -export const useTable = ( - options: TableOptions -) => { - // Compose in the generic options to the user options - const resolvedOptions: TableOptionsResolved = { - state: {}, // Dummy state - onStateChange: () => {}, // noop - renderFallbackValue: null, - ...options, - } - - // Create a new table - const table = createTable(resolvedOptions) - - // By default, manage table state here using the table's initial state - const state = atom(table.initialState) - - // Subscribe to state changes - state.subscribe(currentState => { - table.setOptions(prev => ({ - ...prev, - ...options, - state: { - ...currentState, - ...options.state, - }, - // Similarly, we'll maintain both our internal state and any user-provided state - onStateChange: updater => { - if (typeof updater === 'function') { - const newState = updater(currentState) - state.set(newState) - } else { - state.set(updater) - } - options.onStateChange?.(updater) - }, - })) - }) - - return table -} diff --git a/examples/vanilla/basic/tsconfig.json b/examples/vanilla/basic/tsconfig.json index 3141563c8a..02eb5e4c87 100644 --- a/examples/vanilla/basic/tsconfig.json +++ b/examples/vanilla/basic/tsconfig.json @@ -4,8 +4,6 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, @@ -15,12 +13,11 @@ "jsx": "react-jsx", "experimentalDecorators": true, "useDefineForClassFields": false, - - /* Linting */ "strict": true, "noUnusedLocals": false, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, - "include": ["src"] + "include": ["src", "vite.config.js", "vite.config.ts"] } diff --git a/examples/vanilla/pagination/index.html b/examples/vanilla/pagination/index.html index 1cff7ebfc1..808a0111e1 100644 --- a/examples/vanilla/pagination/index.html +++ b/examples/vanilla/pagination/index.html @@ -4,10 +4,9 @@ Vite + TS - -
+
diff --git a/examples/vanilla/pagination/package.json b/examples/vanilla/pagination/package.json index 9d7891993f..a097a3b609 100644 --- a/examples/vanilla/pagination/package.json +++ b/examples/vanilla/pagination/package.json @@ -1,6 +1,5 @@ { - "name": "tanstack-table-example-vanilla-pagination", - "version": "0.0.0", + "name": "tanstack-vanilla-table-example-pagination", "private": true, "scripts": { "dev": "vite", @@ -9,13 +8,13 @@ "start": "vite" }, "devDependencies": { - "@faker-js/faker": "^8.4.1", - "@rollup/plugin-replace": "^5.0.7", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@types/node": "^25.6.2", + "typescript": "6.0.3", + "vite": "^8.0.11" }, "dependencies": { - "@tanstack/table-core": "^8.20.5", - "nanostores": "^0.11.3" + "@tanstack/table-core": "^9.0.0-alpha.43" } } diff --git a/examples/vanilla/pagination/src/index.css b/examples/vanilla/pagination/src/index.css index 28e95652af..443b3983a6 100644 --- a/examples/vanilla/pagination/src/index.css +++ b/examples/vanilla/pagination/src/index.css @@ -5,6 +5,8 @@ html { table { border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; } tbody { @@ -28,3 +30,340 @@ tfoot th { button:disabled { opacity: 0.5; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/vanilla/pagination/src/main.ts b/examples/vanilla/pagination/src/main.ts index b2173b6d80..6c5832ad1f 100644 --- a/examples/vanilla/pagination/src/main.ts +++ b/examples/vanilla/pagination/src/main.ts @@ -1,80 +1,99 @@ import './index.css' import { + constructTable, createColumnHelper, - getCoreRowModel, - getPaginationRowModel, - getSortedRowModel, + createPaginatedRowModel, + rowPaginationFeature, + tableFeatures, } from '@tanstack/table-core' +import { FlexRender } from '@tanstack/table-core/flex-render' +import { storeReactivityBindings } from '@tanstack/table-core/store-reactivity-bindings' +import { makeData } from './makeData' +import type { Person } from './makeData' +import type { Table } from '@tanstack/table-core' -import { makeData, Person } from './makeData' -import { flexRender, useTable } from './useTable' +let data = makeData(200_000) -const data = makeData(100000) +const _features = tableFeatures({ + rowPaginationFeature, + coreReativityFeature: storeReactivityBindings(), +}) -const columnHelper = createColumnHelper() +const columnHelper = createColumnHelper() -const columns = [ +const columns = columnHelper.columns([ columnHelper.accessor('firstName', { - cell: info => info.getValue(), - footer: info => info.column.id, + cell: (info) => info.getValue(), + footer: (info) => info.column.id, }), - columnHelper.accessor(row => row.lastName, { + columnHelper.accessor((row) => row.lastName, { id: 'lastName', - cell: info => `${info.getValue()}`, + cell: (info) => `${info.getValue()}`, header: () => 'Last Name', - footer: info => info.column.id, + footer: (info) => info.column.id, }), columnHelper.accessor('age', { header: () => 'Age', - cell: info => info.renderValue(), - footer: info => info.column.id, + cell: (info) => info.renderValue(), + footer: (info) => info.column.id, }), columnHelper.accessor('visits', { header: () => 'Visits', - footer: info => info.column.id, + footer: (info) => info.column.id, }), columnHelper.accessor('status', { header: 'Status', - footer: info => info.column.id, + footer: (info) => info.column.id, }), columnHelper.accessor('progress', { header: 'Profile Progress', - footer: info => info.column.id, + footer: (info) => info.column.id, }), -] +]) + +const renderTable = (table: Table) => { + // Create buttons container + const buttonsDiv = document.createElement('div') + + const regenerateBtn = document.createElement('button') + regenerateBtn.textContent = 'Regenerate Data' + regenerateBtn.addEventListener('click', () => { + data = makeData(1_000) + table.setOptions((prev) => ({ ...prev, data })) + }) + + const stressTestBtn = document.createElement('button') + stressTestBtn.textContent = 'Stress Test (200k rows)' + stressTestBtn.addEventListener('click', () => { + data = makeData(200_000) + table.setOptions((prev) => ({ ...prev, data })) + }) + + buttonsDiv.appendChild(regenerateBtn) + buttonsDiv.appendChild(stressTestBtn) -const renderTable = () => { // Create table elements const tableElement = document.createElement('table') const theadElement = document.createElement('thead') const tbodyElement = document.createElement('tbody') - tableElement.classList.add('mb-2') + tableElement.classList.add('table-spacer') tableElement.appendChild(theadElement) tableElement.appendChild(tbodyElement) // Render table headers - table.getHeaderGroups().forEach(headerGroup => { + table.getHeaderGroups().forEach((headerGroup) => { const trElement = document.createElement('tr') - headerGroup.headers.forEach(header => { + headerGroup.headers.forEach((header) => { const thElement = document.createElement('th') thElement.colSpan = header.colSpan const divElement = document.createElement('div') - divElement.classList.add( - 'w-36', - ...(header.column.getCanSort() ? ['cursor-pointer', 'select-none'] : []) - ) - ;(divElement.onclick = e => header.column.getToggleSortingHandler()?.(e)), - (divElement.innerHTML = header.isPlaceholder - ? '' - : flexRender(header.column.columnDef.header, header.getContext())) - divElement.innerHTML += - { - asc: ' 🔼', - desc: ' 🔽', - }[header.column.getIsSorted() as string] ?? '' + divElement.classList.add('w-36') + divElement.innerHTML = header.isPlaceholder + ? '' + : String(FlexRender({ header }) ?? '') thElement.appendChild(divElement) trElement.appendChild(thElement) }) @@ -82,14 +101,11 @@ const renderTable = () => { }) // Render table rows - table.getRowModel().rows.forEach(row => { + table.getRowModel().rows.forEach((row) => { const trElement = document.createElement('tr') - row.getVisibleCells().forEach(cell => { + row.getAllCells().forEach((cell) => { const tdElement = document.createElement('td') - tdElement.innerHTML = flexRender( - cell.column.columnDef.cell, - cell.getContext() - ) + tdElement.innerHTML = String(FlexRender({ cell }) ?? '') trElement.appendChild(tdElement) }) tbodyElement.appendChild(trElement) @@ -97,11 +113,11 @@ const renderTable = () => { // Render pagination const paginationElement = document.createElement('div') - paginationElement.classList.add('flex', 'items-center', 'gap-2') + paginationElement.classList.add('table-row-group', 'controls', 'controls') // Render pagination first page button const firstPageButton = document.createElement('button') - firstPageButton.classList.add('border', 'rounded', 'p-1') + firstPageButton.classList.add('demo-button', 'demo-button', 'cell-padding') firstPageButton.disabled = !table.getCanPreviousPage() firstPageButton.innerHTML = '<<' firstPageButton.onclick = () => table.firstPage() @@ -109,7 +125,7 @@ const renderTable = () => { // Render pagination previous page button const prevPageButton = document.createElement('button') - prevPageButton.classList.add('border', 'rounded', 'p-1') + prevPageButton.classList.add('demo-button', 'demo-button', 'cell-padding') prevPageButton.disabled = !table.getCanPreviousPage() prevPageButton.innerHTML = '<' prevPageButton.onclick = () => table.previousPage() @@ -117,7 +133,7 @@ const renderTable = () => { // Render pagination next page button const nextPageButton = document.createElement('button') - nextPageButton.classList.add('border', 'rounded', 'p-1') + nextPageButton.classList.add('demo-button', 'demo-button', 'cell-padding') nextPageButton.disabled = !table.getCanNextPage() nextPageButton.innerHTML = '>' nextPageButton.onclick = () => table.nextPage() @@ -125,7 +141,7 @@ const renderTable = () => { // Render pagination last page button const lastPageButton = document.createElement('button') - lastPageButton.classList.add('border', 'rounded', 'p-1') + lastPageButton.classList.add('demo-button', 'demo-button', 'cell-padding') lastPageButton.disabled = !table.getCanNextPage() lastPageButton.innerHTML = '>>' lastPageButton.onclick = () => table.lastPage() @@ -133,23 +149,38 @@ const renderTable = () => { // Render pagination info const paginationInfoElement = document.createElement('span') - paginationInfoElement.classList.add('flex', 'items-center', 'gap-1') - paginationInfoElement.innerHTML = `
Page
${table.getState().pagination.pageIndex + 1} of ${table.getPageCount().toLocaleString()}` + paginationInfoElement.classList.add( + 'table-row-group', + 'controls', + 'inline-controls', + ) + paginationInfoElement.innerHTML = `
Page
${( + table.store.state.pagination.pageIndex + 1 + ).toLocaleString()} of ${table.getPageCount().toLocaleString()}` paginationElement.appendChild(paginationInfoElement) // Render pagination set page const paginationPageElement = document.createElement('span') - paginationPageElement.classList.add('flex', 'items-center', 'gap-1') + paginationPageElement.classList.add( + 'table-row-group', + 'controls', + 'inline-controls', + ) paginationPageElement.textContent = '| Go to page:' const paginationPageInput = document.createElement('input') paginationPageInput.type = 'number' paginationPageInput.min = String(1) paginationPageInput.max = String(table.getPageCount()) paginationPageInput.defaultValue = String( - table.getState().pagination.pageIndex + 1 + table.store.state.pagination.pageIndex + 1, ) - paginationPageInput.classList.add('border', 'p-1', 'rounded', 'w-16') - paginationPageInput.oninput = e => { + paginationPageInput.classList.add( + 'demo-button', + 'cell-padding', + 'demo-button', + 'page-size-input', + ) + paginationPageInput.oninput = (e) => { const target = e.target as HTMLInputElement const page = target.value ? Number(target.value) - 1 : 0 table.setPageIndex(page) @@ -159,15 +190,15 @@ const renderTable = () => { // Render pagiantion page size const paginationPageSizeSelect = document.createElement('select') - paginationPageSizeSelect.value = String(table.getState().pagination.pageSize) - paginationPageSizeSelect.onchange = e => { + paginationPageSizeSelect.value = String(table.store.state.pagination.pageSize) + paginationPageSizeSelect.onchange = (e) => { const target = e.target as HTMLSelectElement table.setPageSize(Number(target.value)) } - ;[10, 20, 30, 40, 50].map(pageSize => { + ;[10, 20, 30, 40, 50].map((pageSize) => { const option = document.createElement('option') option.value = String(pageSize) - option.selected = table.getState().pagination.pageSize === pageSize + option.selected = table.store.state.pagination.pageSize === pageSize option.textContent = `Show ${pageSize}` paginationPageSizeSelect.appendChild(option) }) @@ -177,39 +208,37 @@ const renderTable = () => { const stateInfoElement = document.createElement('pre') stateInfoElement.textContent = JSON.stringify( { - pagination: table.getState().pagination, - sorting: table.getState().sorting, + pagination: table.store.state.pagination, }, null, - 2 + 2, ) // Clear previous content and append new content const wrapperElement = document.getElementById('wrapper') as HTMLDivElement wrapperElement.innerHTML = '' + wrapperElement.appendChild(buttonsDiv) wrapperElement.appendChild(tableElement) wrapperElement.appendChild(paginationElement) wrapperElement.appendChild(stateInfoElement) } -const table = useTable({ +const table = constructTable({ + _features, + _rowModels: { + paginatedRowModel: createPaginatedRowModel(), + }, data, columns, initialState: { pagination: { + pageIndex: 0, pageSize: 10, }, - sorting: [ - { - id: 'lastName', - desc: false, - }, - ], }, - getCoreRowModel: getCoreRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getSortedRowModel: getSortedRowModel(), - onStateChange: () => renderTable(), + debugTable: true, }) -renderTable() +table.store.subscribe(() => renderTable(table)) + +renderTable(table) diff --git a/examples/vanilla/pagination/src/makeData.ts b/examples/vanilla/pagination/src/makeData.ts index 331dd1eb19..b9055b2d8c 100644 --- a/examples/vanilla/pagination/src/makeData.ts +++ b/examples/vanilla/pagination/src/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,14 +29,14 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! - return range(len).map((d): Person => { +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { return { ...newPerson(), subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, diff --git a/examples/vanilla/pagination/src/useTable.ts b/examples/vanilla/pagination/src/useTable.ts deleted file mode 100644 index df476ee682..0000000000 --- a/examples/vanilla/pagination/src/useTable.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { atom } from 'nanostores' - -import { - type RowData, - type TableOptions, - type TableOptionsResolved, - createTable, -} from '@tanstack/table-core' - -export const flexRender = (comp: any, props: TProps) => { - if (typeof comp === 'function') { - return comp(props) - } - return comp -} - -export const useTable = ( - options: TableOptions -) => { - // Compose in the generic options to the user options - const resolvedOptions: TableOptionsResolved = { - state: {}, // Dummy state - onStateChange: () => {}, // noop - renderFallbackValue: null, - ...options, - } - - // Create a new table - const table = createTable(resolvedOptions) - - // By default, manage table state here using the table's initial state - const state = atom(table.initialState) - - // Subscribe to state changes - state.subscribe(currentState => { - table.setOptions(prev => ({ - ...prev, - ...options, - state: { - ...currentState, - ...options.state, - }, - // Similarly, we'll maintain both our internal state and any user-provided state - onStateChange: updater => { - if (typeof updater === 'function') { - const newState = updater(currentState) - state.set(newState) - } else { - state.set(updater) - } - options.onStateChange?.(updater) - }, - })) - }) - - return table -} diff --git a/examples/vanilla/pagination/tsconfig.json b/examples/vanilla/pagination/tsconfig.json index 3141563c8a..02eb5e4c87 100644 --- a/examples/vanilla/pagination/tsconfig.json +++ b/examples/vanilla/pagination/tsconfig.json @@ -4,8 +4,6 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, @@ -15,12 +13,11 @@ "jsx": "react-jsx", "experimentalDecorators": true, "useDefineForClassFields": false, - - /* Linting */ "strict": true, "noUnusedLocals": false, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, - "include": ["src"] + "include": ["src", "vite.config.js", "vite.config.ts"] } diff --git a/examples/vanilla/sorting/index.html b/examples/vanilla/sorting/index.html index 1cff7ebfc1..808a0111e1 100644 --- a/examples/vanilla/sorting/index.html +++ b/examples/vanilla/sorting/index.html @@ -4,10 +4,9 @@ Vite + TS - -
+
diff --git a/examples/vanilla/sorting/package.json b/examples/vanilla/sorting/package.json index b3674d1023..c9f1749807 100644 --- a/examples/vanilla/sorting/package.json +++ b/examples/vanilla/sorting/package.json @@ -1,6 +1,5 @@ { - "name": "tanstack-table-example-vanilla-sorting", - "version": "0.0.0", + "name": "tanstack-vanilla-table-example-sorting", "private": true, "scripts": { "dev": "vite", @@ -9,13 +8,13 @@ "start": "vite" }, "devDependencies": { - "@faker-js/faker": "^8.4.1", - "@rollup/plugin-replace": "^5.0.7", - "typescript": "5.4.5", - "vite": "^5.3.2" + "@faker-js/faker": "^10.4.0", + "@rollup/plugin-replace": "^6.0.3", + "@types/node": "^25.6.2", + "typescript": "6.0.3", + "vite": "^8.0.11" }, "dependencies": { - "@tanstack/table-core": "^8.20.5", - "nanostores": "^0.11.3" + "@tanstack/table-core": "^9.0.0-alpha.43" } } diff --git a/examples/vanilla/sorting/src/index.css b/examples/vanilla/sorting/src/index.css index 28e95652af..443b3983a6 100644 --- a/examples/vanilla/sorting/src/index.css +++ b/examples/vanilla/sorting/src/index.css @@ -5,6 +5,8 @@ html { table { border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; } tbody { @@ -28,3 +30,340 @@ tfoot th { button:disabled { opacity: 0.5; } + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/vanilla/sorting/src/main.ts b/examples/vanilla/sorting/src/main.ts index a568519321..6319551908 100644 --- a/examples/vanilla/sorting/src/main.ts +++ b/examples/vanilla/sorting/src/main.ts @@ -1,42 +1,50 @@ import './index.css' - import { - type SortingFn, + constructTable, createColumnHelper, - getCoreRowModel, - getSortedRowModel, + createSortedRowModel, + rowSortingFeature, + sortFns, + tableFeatures, } from '@tanstack/table-core' +import { FlexRender } from '@tanstack/table-core/flex-render' +import { storeReactivityBindings } from '@tanstack/table-core/store-reactivity-bindings' +import { makeData } from './makeData' +import type { SortFn } from '@tanstack/table-core' +import type { Person } from './makeData' -import { makeData, Person } from './makeData' -import { flexRender, useTable } from './useTable' +let data = makeData(1_000) -const data = makeData(1000) +const _features = tableFeatures({ + rowSortingFeature, + coreReativityFeature: storeReactivityBindings(), +}) // Custom sorting logic for one of our enum columns -const sortStatusFn: SortingFn = (rowA, rowB, _columnId) => { +const sortStatusFn: SortFn = (rowA, rowB, _columnId) => { const statusA = rowA.original.status const statusB = rowB.original.status const statusOrder = ['single', 'complicated', 'relationship'] return statusOrder.indexOf(statusA) - statusOrder.indexOf(statusB) } -const columnHelper = createColumnHelper() +const columnHelper = createColumnHelper() -const columns = [ +const columns = columnHelper.columns([ columnHelper.accessor('firstName', { - cell: info => info.getValue(), + cell: (info) => info.getValue(), // This column will sort in ascending order by default since it is a string column }), - columnHelper.accessor(row => row.lastName, { + columnHelper.accessor((row) => row.lastName, { id: 'lastName', - cell: info => `${info.getValue()}`, + cell: (info) => `${info.getValue()}`, header: () => 'Last Name', sortUndefined: 'last', // Force undefined values to the end sortDescFirst: false, // First sort order will be ascending (nullable values can mess up auto detection of sort order) }), columnHelper.accessor('age', { header: () => 'Age', - cell: info => info.renderValue(), + cell: (info) => info.renderValue(), // This column will sort in descending order by default since it is a number column }), columnHelper.accessor('visits', { @@ -45,7 +53,7 @@ const columns = [ }), columnHelper.accessor('status', { header: 'Status', - sortingFn: sortStatusFn, // Use our custom sorting function for this enum column + sortFn: sortStatusFn, // Use our custom sorting function for this enum column }), columnHelper.accessor('progress', { header: 'Profile Progress', @@ -58,34 +66,57 @@ const columns = [ columnHelper.accessor('createdAt', { header: 'Created At', }), -] +]) const renderTable = () => { + // Create buttons container + const buttonsDiv = document.createElement('div') + + const regenerateBtn = document.createElement('button') + regenerateBtn.textContent = 'Regenerate Data' + regenerateBtn.addEventListener('click', () => { + data = makeData(1_000) + table.setOptions((prev) => ({ ...prev, data })) + }) + + const stressTestBtn = document.createElement('button') + stressTestBtn.textContent = 'Stress Test (500k rows)' + stressTestBtn.addEventListener('click', () => { + data = makeData(500_000) + table.setOptions((prev) => ({ ...prev, data })) + }) + + buttonsDiv.appendChild(regenerateBtn) + buttonsDiv.appendChild(stressTestBtn) + // Create table elements const tableElement = document.createElement('table') const theadElement = document.createElement('thead') const tbodyElement = document.createElement('tbody') - tableElement.classList.add('mb-2') + tableElement.classList.add('table-spacer') tableElement.appendChild(theadElement) tableElement.appendChild(tbodyElement) // Render table headers - table.getHeaderGroups().forEach(headerGroup => { + table.getHeaderGroups().forEach((headerGroup) => { const trElement = document.createElement('tr') - headerGroup.headers.forEach(header => { + headerGroup.headers.forEach((header) => { const thElement = document.createElement('th') thElement.colSpan = header.colSpan const divElement = document.createElement('div') divElement.classList.add( 'w-36', - ...(header.column.getCanSort() ? ['cursor-pointer', 'select-none'] : []) + ...(header.column.getCanSort() + ? ['sortable-header', 'sortable-header'] + : []), ) - ;(divElement.onclick = e => header.column.getToggleSortingHandler()?.(e)), + ;((divElement.onclick = (e) => + header.column.getToggleSortingHandler()?.(e)), (divElement.innerHTML = header.isPlaceholder ? '' - : flexRender(header.column.columnDef.header, header.getContext())) + : String(FlexRender({ header }) ?? ''))) divElement.innerHTML += { asc: ' 🔼', @@ -101,14 +132,11 @@ const renderTable = () => { table .getRowModel() .rows.slice(0, 10) - .forEach(row => { + .forEach((row) => { const trElement = document.createElement('tr') - row.getVisibleCells().forEach(cell => { + row.getAllCells().forEach((cell) => { const tdElement = document.createElement('td') - tdElement.innerHTML = flexRender( - cell.column.columnDef.cell, - cell.getContext() - ) + tdElement.innerHTML = String(FlexRender({ cell }) ?? '') trElement.appendChild(tdElement) }) tbodyElement.appendChild(trElement) @@ -118,25 +146,30 @@ const renderTable = () => { const stateInfoElement = document.createElement('pre') stateInfoElement.textContent = JSON.stringify( { - sorting: table.getState().sorting, + sorting: table.store.state.sorting, }, null, - 2 + 2, ) // Clear previous content and append new content const wrapperElement = document.getElementById('wrapper') as HTMLDivElement wrapperElement.innerHTML = '' + wrapperElement.appendChild(buttonsDiv) wrapperElement.appendChild(tableElement) wrapperElement.appendChild(stateInfoElement) } -const table = useTable({ +const table = constructTable({ + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + }, data, columns, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), - onStateChange: () => renderTable(), + debugTable: true, }) +table.store.subscribe(() => renderTable()) + renderTable() diff --git a/examples/vanilla/sorting/src/makeData.ts b/examples/vanilla/sorting/src/makeData.ts index 1ff5e80558..3c30d9404d 100644 --- a/examples/vanilla/sorting/src/makeData.ts +++ b/examples/vanilla/sorting/src/makeData.ts @@ -9,11 +9,11 @@ export type Person = { status: 'relationship' | 'complicated' | 'single' rank: number createdAt: Date - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -31,16 +31,16 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], rank: faker.number.int(100), createdAt: faker.date.anytime(), } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! - return range(len).map((d): Person => { +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { return { ...newPerson(), subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, diff --git a/examples/vanilla/sorting/src/useTable.ts b/examples/vanilla/sorting/src/useTable.ts deleted file mode 100644 index df476ee682..0000000000 --- a/examples/vanilla/sorting/src/useTable.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { atom } from 'nanostores' - -import { - type RowData, - type TableOptions, - type TableOptionsResolved, - createTable, -} from '@tanstack/table-core' - -export const flexRender = (comp: any, props: TProps) => { - if (typeof comp === 'function') { - return comp(props) - } - return comp -} - -export const useTable = ( - options: TableOptions -) => { - // Compose in the generic options to the user options - const resolvedOptions: TableOptionsResolved = { - state: {}, // Dummy state - onStateChange: () => {}, // noop - renderFallbackValue: null, - ...options, - } - - // Create a new table - const table = createTable(resolvedOptions) - - // By default, manage table state here using the table's initial state - const state = atom(table.initialState) - - // Subscribe to state changes - state.subscribe(currentState => { - table.setOptions(prev => ({ - ...prev, - ...options, - state: { - ...currentState, - ...options.state, - }, - // Similarly, we'll maintain both our internal state and any user-provided state - onStateChange: updater => { - if (typeof updater === 'function') { - const newState = updater(currentState) - state.set(newState) - } else { - state.set(updater) - } - options.onStateChange?.(updater) - }, - })) - }) - - return table -} diff --git a/examples/vanilla/sorting/tsconfig.json b/examples/vanilla/sorting/tsconfig.json index 3141563c8a..02eb5e4c87 100644 --- a/examples/vanilla/sorting/tsconfig.json +++ b/examples/vanilla/sorting/tsconfig.json @@ -4,8 +4,6 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, @@ -15,12 +13,11 @@ "jsx": "react-jsx", "experimentalDecorators": true, "useDefineForClassFields": false, - - /* Linting */ "strict": true, "noUnusedLocals": false, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowJs": true }, - "include": ["src"] + "include": ["src", "vite.config.js", "vite.config.ts"] } diff --git a/examples/vue/basic/README.md b/examples/vue/basic-external-atoms/README.md similarity index 100% rename from examples/vue/basic/README.md rename to examples/vue/basic-external-atoms/README.md diff --git a/examples/vue/basic-external-atoms/env.d.ts b/examples/vue/basic-external-atoms/env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/vue/basic-external-atoms/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/vue/basic-external-atoms/index.html b/examples/vue/basic-external-atoms/index.html new file mode 100644 index 0000000000..dcd696757f --- /dev/null +++ b/examples/vue/basic-external-atoms/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/examples/vue/basic-external-atoms/package.json b/examples/vue/basic-external-atoms/package.json new file mode 100644 index 0000000000..023a80baf4 --- /dev/null +++ b/examples/vue/basic-external-atoms/package.json @@ -0,0 +1,25 @@ +{ + "name": "tanstack-vue-table-example-basic-external-atoms", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint ./src", + "test:types": "vue-tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/vue-store": "^0.11.0", + "@tanstack/vue-table": "^9.0.0-alpha.45", + "vue": "^3.5.34" + }, + "devDependencies": { + "@types/node": "^25.6.2", + "@vitejs/plugin-vue": "^6.0.6", + "@vitejs/plugin-vue-jsx": "^5.1.5", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vue-tsc": "^3.2.8" + } +} diff --git a/examples/vue/basic-external-atoms/src/App.tsx b/examples/vue/basic-external-atoms/src/App.tsx new file mode 100644 index 0000000000..77a65b2cd9 --- /dev/null +++ b/examples/vue/basic-external-atoms/src/App.tsx @@ -0,0 +1,229 @@ +import { defineComponent, ref } from 'vue' +import { createAtom, useSelector } from '@tanstack/vue-store' +import { + FlexRender, + createColumnHelper, + createPaginatedRowModel, + createSortedRowModel, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, + useTable, +} from '@tanstack/vue-table' +import { makeData } from './makeData' +import type { + Cell, + Header, + HeaderGroup, + PaginationState, + Row, + SortingState, +} from '@tanstack/vue-table' +import type { Person } from './makeData' + +const _features = tableFeatures({ + rowPaginationFeature, + rowSortingFeature, +}) + +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('lastName', { + header: 'Last Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('age', { + header: 'Age', + }), + columnHelper.accessor('visits', { + header: 'Visits', + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + }), +]) + +export default defineComponent({ + name: 'BasicExternalAtomsExample', + setup() { + const data = ref(makeData(1_000)) + + const refreshData = () => { + data.value = makeData(1_000) + } + + const stressTest = () => { + data.value = makeData(200_000) + } + + const sortingAtom = createAtom([]) + const paginationAtom = createAtom({ + pageIndex: 0, + pageSize: 10, + }) + + const sorting = useSelector(sortingAtom) + const pagination = useSelector(paginationAtom) + + const table = useTable({ + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + get data() { + return data.value + }, + atoms: { + sorting: sortingAtom, + pagination: paginationAtom, + }, + debugTable: true, + }) + + return () => ( +
+
+ + +
+
+ + + {table + .getHeaderGroups() + .map((headerGroup: HeaderGroup) => ( + + {headerGroup.headers.map( + (header: Header) => ( + + ), + )} + + ))} + + + {table + .getRowModel() + .rows.map((row: Row) => ( + + {row + .getAllCells() + .map((cell: Cell) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( +
+ + {{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null} +
+ )} +
+ +
+
+
+ + + + + +
Page
+ + {(pagination.value.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const target = event.currentTarget as HTMLInputElement + const page = target.value ? Number(target.value) - 1 : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
+
+
+          {JSON.stringify(
+            { sorting: sorting.value, pagination: pagination.value },
+            null,
+            2,
+          )}
+        
+
+ ) + }, +}) diff --git a/examples/vue/basic-external-atoms/src/App.vue b/examples/vue/basic-external-atoms/src/App.vue new file mode 100644 index 0000000000..4256c2000a --- /dev/null +++ b/examples/vue/basic-external-atoms/src/App.vue @@ -0,0 +1,7 @@ + + + diff --git a/examples/vue/basic-external-atoms/src/env.d.ts b/examples/vue/basic-external-atoms/src/env.d.ts new file mode 100644 index 0000000000..6977c557dc --- /dev/null +++ b/examples/vue/basic-external-atoms/src/env.d.ts @@ -0,0 +1,8 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/examples/vue/basic-external-atoms/src/index.css b/examples/vue/basic-external-atoms/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/vue/basic-external-atoms/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/vue/basic-external-atoms/src/main.ts b/examples/vue/basic-external-atoms/src/main.ts new file mode 100644 index 0000000000..50a4dab0d5 --- /dev/null +++ b/examples/vue/basic-external-atoms/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import App from './App.vue' +import './index.css' + +createApp(App).mount('#app') diff --git a/examples/vue/basic-external-atoms/src/makeData.ts b/examples/vue/basic-external-atoms/src/makeData.ts new file mode 100644 index 0000000000..6311127267 --- /dev/null +++ b/examples/vue/basic-external-atoms/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/vue/basic-external-atoms/tsconfig.json b/examples/vue/basic-external-atoms/tsconfig.json new file mode 100644 index 0000000000..500d5a5148 --- /dev/null +++ b/examples/vue/basic-external-atoms/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/vue/basic-external-atoms/vite.config.ts b/examples/vue/basic-external-atoms/vite.config.ts new file mode 100644 index 0000000000..3dec1c870d --- /dev/null +++ b/examples/vue/basic-external-atoms/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' + +export default defineConfig({ + plugins: [vue(), vueJsx()], +}) diff --git a/examples/vue/pagination-controlled/README.md b/examples/vue/basic-external-state/README.md similarity index 100% rename from examples/vue/pagination-controlled/README.md rename to examples/vue/basic-external-state/README.md diff --git a/examples/vue/basic-external-state/env.d.ts b/examples/vue/basic-external-state/env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/vue/basic-external-state/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/vue/basic-external-state/index.html b/examples/vue/basic-external-state/index.html new file mode 100644 index 0000000000..dcd696757f --- /dev/null +++ b/examples/vue/basic-external-state/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/examples/vue/basic-external-state/package.json b/examples/vue/basic-external-state/package.json new file mode 100644 index 0000000000..c6335ee7b8 --- /dev/null +++ b/examples/vue/basic-external-state/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-vue-table-example-basic-external-state", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint ./src", + "test:types": "vue-tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/vue-table": "^9.0.0-alpha.45", + "vue": "^3.5.34" + }, + "devDependencies": { + "@types/node": "^25.6.2", + "@vitejs/plugin-vue": "^6.0.6", + "@vitejs/plugin-vue-jsx": "^5.1.5", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vue-tsc": "^3.2.8" + } +} diff --git a/examples/vue/basic-external-state/src/App.tsx b/examples/vue/basic-external-state/src/App.tsx new file mode 100644 index 0000000000..04c24679cd --- /dev/null +++ b/examples/vue/basic-external-state/src/App.tsx @@ -0,0 +1,251 @@ +import { defineComponent, ref } from 'vue' +import { + FlexRender, + createColumnHelper, + createPaginatedRowModel, + createSortedRowModel, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, + useTable, +} from '@tanstack/vue-table' +import { makeData } from './makeData' +import type { + Cell, + Header, + HeaderGroup, + PaginationState, + Row, + SortingState, + Updater, +} from '@tanstack/vue-table' +import type { Person } from './makeData' + +const _features = tableFeatures({ + rowPaginationFeature, + rowSortingFeature, +}) + +const columnHelper = createColumnHelper() + +const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + header: 'First Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('lastName', { + header: 'Last Name', + cell: (info) => info.getValue(), + }), + columnHelper.accessor('age', { + header: 'Age', + }), + columnHelper.accessor('visits', { + header: 'Visits', + }), + columnHelper.accessor('status', { + header: 'Status', + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + }), +]) + +function resolveUpdater(updater: Updater, previous: T): T { + return typeof updater === 'function' + ? (updater as (old: T) => T)(previous) + : updater +} + +export default defineComponent({ + name: 'BasicExternalStateExample', + setup() { + const data = ref(makeData(1_000)) + + const refreshData = () => { + data.value = makeData(1_000) + } + + const stressTest = () => { + data.value = makeData(200_000) + } + + const sorting = ref([]) + const pagination = ref({ + pageIndex: 0, + pageSize: 10, + }) + + const table = useTable( + { + debugTable: true, + _features, + _rowModels: { + sortedRowModel: createSortedRowModel(sortFns), + paginatedRowModel: createPaginatedRowModel(), + }, + columns, + get data() { + return data.value + }, + state: { + get sorting() { + return sorting.value + }, + get pagination() { + return pagination.value + }, + }, + onSortingChange: (updater: Updater) => { + sorting.value = resolveUpdater(updater, sorting.value) + }, + onPaginationChange: (updater: Updater) => { + pagination.value = resolveUpdater(updater, pagination.value) + }, + }, + (state) => ({ + sorting: state.sorting, + pagination: state.pagination, + }), + ) + + return () => ( +
+
+ + +
+
+ + + {table + .getHeaderGroups() + .map((headerGroup: HeaderGroup) => ( + + {headerGroup.headers.map( + (header: Header) => ( + + ), + )} + + ))} + + + {table + .getRowModel() + .rows.map((row: Row) => ( + + {row + .getAllCells() + .map((cell: Cell) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( +
+ + {{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null} +
+ )} +
+ +
+
+
+ + + + + +
Page
+ + {(table.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const target = event.currentTarget as HTMLInputElement + const page = target.value ? Number(target.value) - 1 : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
+
+
+          {JSON.stringify(
+            {
+              sorting: table.state.sorting,
+              pagination: table.state.pagination,
+            },
+            null,
+            2,
+          )}
+        
+
+ ) + }, +}) diff --git a/examples/vue/basic-external-state/src/App.vue b/examples/vue/basic-external-state/src/App.vue new file mode 100644 index 0000000000..4256c2000a --- /dev/null +++ b/examples/vue/basic-external-state/src/App.vue @@ -0,0 +1,7 @@ + + + diff --git a/examples/vue/basic-external-state/src/env.d.ts b/examples/vue/basic-external-state/src/env.d.ts new file mode 100644 index 0000000000..6977c557dc --- /dev/null +++ b/examples/vue/basic-external-state/src/env.d.ts @@ -0,0 +1,8 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/examples/vue/basic-external-state/src/index.css b/examples/vue/basic-external-state/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/vue/basic-external-state/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/vue/basic-external-state/src/main.ts b/examples/vue/basic-external-state/src/main.ts new file mode 100644 index 0000000000..50a4dab0d5 --- /dev/null +++ b/examples/vue/basic-external-state/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import App from './App.vue' +import './index.css' + +createApp(App).mount('#app') diff --git a/examples/vue/basic-external-state/src/makeData.ts b/examples/vue/basic-external-state/src/makeData.ts new file mode 100644 index 0000000000..6311127267 --- /dev/null +++ b/examples/vue/basic-external-state/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/vue/basic-external-state/tsconfig.json b/examples/vue/basic-external-state/tsconfig.json new file mode 100644 index 0000000000..500d5a5148 --- /dev/null +++ b/examples/vue/basic-external-state/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/vue/basic-external-state/vite.config.ts b/examples/vue/basic-external-state/vite.config.ts new file mode 100644 index 0000000000..3dec1c870d --- /dev/null +++ b/examples/vue/basic-external-state/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' + +export default defineConfig({ + plugins: [vue(), vueJsx()], +}) diff --git a/examples/vue/basic-use-app-table/README.md b/examples/vue/basic-use-app-table/README.md new file mode 100644 index 0000000000..3ac3f1a9b4 --- /dev/null +++ b/examples/vue/basic-use-app-table/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run dev` or `yarn dev` diff --git a/examples/vue/basic-use-app-table/index.html b/examples/vue/basic-use-app-table/index.html new file mode 100644 index 0000000000..2504a0f027 --- /dev/null +++ b/examples/vue/basic-use-app-table/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Table Vue Basic useAppTable + + +
+ + + diff --git a/examples/vue/basic-use-app-table/package.json b/examples/vue/basic-use-app-table/package.json new file mode 100644 index 0000000000..65d8fd5edf --- /dev/null +++ b/examples/vue/basic-use-app-table/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-vue-table-example-basic-use-app-table", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint ./src", + "test:types": "vue-tsc" + }, + "dependencies": { + "@tanstack/vue-table": "^9.0.0-alpha.45", + "vue": "^3.5.34" + }, + "devDependencies": { + "@types/node": "^25.6.2", + "@vitejs/plugin-vue": "^6.0.6", + "@vitejs/plugin-vue-jsx": "^5.1.5", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vue-tsc": "^3.2.8" + } +} diff --git a/examples/vue/basic-use-app-table/src/App.vue b/examples/vue/basic-use-app-table/src/App.vue new file mode 100644 index 0000000000..e01be6f776 --- /dev/null +++ b/examples/vue/basic-use-app-table/src/App.vue @@ -0,0 +1,187 @@ + + + + + diff --git a/examples/vue/basic-use-app-table/src/env.d.ts b/examples/vue/basic-use-app-table/src/env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/vue/basic-use-app-table/src/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/vue/basic/src/main.ts b/examples/vue/basic-use-app-table/src/main.ts similarity index 100% rename from examples/vue/basic/src/main.ts rename to examples/vue/basic-use-app-table/src/main.ts diff --git a/examples/vue/basic-use-app-table/tsconfig.json b/examples/vue/basic-use-app-table/tsconfig.json new file mode 100644 index 0000000000..500d5a5148 --- /dev/null +++ b/examples/vue/basic-use-app-table/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/vue/basic-use-app-table/vite.config.ts b/examples/vue/basic-use-app-table/vite.config.ts new file mode 100644 index 0000000000..3dec1c870d --- /dev/null +++ b/examples/vue/basic-use-app-table/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' + +export default defineConfig({ + plugins: [vue(), vueJsx()], +}) diff --git a/examples/qwik/row-selection/.gitignore b/examples/vue/basic-use-table/.gitignore similarity index 100% rename from examples/qwik/row-selection/.gitignore rename to examples/vue/basic-use-table/.gitignore diff --git a/examples/vue/basic-use-table/README.md b/examples/vue/basic-use-table/README.md new file mode 100644 index 0000000000..3ac3f1a9b4 --- /dev/null +++ b/examples/vue/basic-use-table/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run dev` or `yarn dev` diff --git a/examples/vue/basic-use-table/env.d.ts b/examples/vue/basic-use-table/env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/vue/basic-use-table/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/vue/basic-use-table/index.html b/examples/vue/basic-use-table/index.html new file mode 100644 index 0000000000..dcd696757f --- /dev/null +++ b/examples/vue/basic-use-table/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/examples/vue/basic-use-table/package.json b/examples/vue/basic-use-table/package.json new file mode 100644 index 0000000000..fa861159b0 --- /dev/null +++ b/examples/vue/basic-use-table/package.json @@ -0,0 +1,23 @@ +{ + "name": "tanstack-vue-table-example-basic-use-table", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint ./src", + "test:types": "vue-tsc" + }, + "dependencies": { + "@tanstack/vue-table": "^9.0.0-alpha.45", + "vue": "^3.5.34" + }, + "devDependencies": { + "@types/node": "^25.6.2", + "@vitejs/plugin-vue": "^6.0.6", + "@vitejs/plugin-vue-jsx": "^5.1.5", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vue-tsc": "^3.2.8" + } +} diff --git a/examples/vue/basic-use-table/src/App.tsx b/examples/vue/basic-use-table/src/App.tsx new file mode 100644 index 0000000000..1e8efc89c6 --- /dev/null +++ b/examples/vue/basic-use-table/src/App.tsx @@ -0,0 +1,168 @@ +import { defineComponent, ref } from 'vue' +import { FlexRender, tableFeatures, useTable } from '@tanstack/vue-table' +import type { + Cell, + ColumnDef, + Header, + HeaderGroup, + Row, +} from '@tanstack/vue-table' + +type Person = { + firstName: string + lastName: string + age: number + visits: number + status: string + progress: number +} + +const defaultData: Array = [ + { + firstName: 'tanner', + lastName: 'linsley', + age: 24, + visits: 100, + status: 'In Relationship', + progress: 50, + }, + { + firstName: 'tandy', + lastName: 'miller', + age: 40, + visits: 40, + status: 'Single', + progress: 80, + }, + { + firstName: 'joe', + lastName: 'dirte', + age: 45, + visits: 20, + status: 'Complicated', + progress: 10, + }, + { + firstName: 'kevin', + lastName: 'vandy', + age: 12, + visits: 100, + status: 'Single', + progress: 70, + }, +] + +const _features = tableFeatures({}) + +const columns: Array> = [ + { + accessorKey: 'firstName', + header: 'First Name', + cell: (info) => info.getValue(), + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + header: () => Last Name, + cell: (info) => {info.getValue()}, + }, + { + accessorFn: (row) => Number(row.age), + id: 'age', + header: () => 'Age', + cell: (info) => info.renderValue(), + }, + { + accessorKey: 'visits', + header: () => Visits, + }, + { + accessorKey: 'status', + header: 'Status', + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + }, +] + +export default defineComponent({ + name: 'BasicUseTableExample', + setup() { + const data = ref([...defaultData]) + + const table = useTable({ + debugTable: true, + _features, + _rowModels: {}, + columns, + get data() { + return data.value + }, + }) + + const rerender = () => { + data.value = [...defaultData] + } + + return () => ( +
+ + + {table + .getHeaderGroups() + .map((headerGroup: HeaderGroup) => ( + + {headerGroup.headers.map( + (header: Header) => ( + + ), + )} + + ))} + + + {table + .getRowModel() + .rows.map((row: Row) => ( + + {row + .getAllCells() + .map((cell: Cell) => ( + + ))} + + ))} + + + {table + .getFooterGroups() + .map((footerGroup: HeaderGroup) => ( + + {footerGroup.headers.map( + (header: Header) => ( + + ), + )} + + ))} + +
+ {header.isPlaceholder ? null : ( + + )} +
+ +
+ {header.isPlaceholder ? null : ( + + )} +
+
+ +
+ ) + }, +}) diff --git a/examples/vue/basic-use-table/src/App.vue b/examples/vue/basic-use-table/src/App.vue new file mode 100644 index 0000000000..4256c2000a --- /dev/null +++ b/examples/vue/basic-use-table/src/App.vue @@ -0,0 +1,7 @@ + + + diff --git a/examples/vue/basic-use-table/src/env.d.ts b/examples/vue/basic-use-table/src/env.d.ts new file mode 100644 index 0000000000..6977c557dc --- /dev/null +++ b/examples/vue/basic-use-table/src/env.d.ts @@ -0,0 +1,8 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/examples/vue/basic-use-table/src/index.css b/examples/vue/basic-use-table/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/vue/basic-use-table/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/vue/basic-use-table/src/main.ts b/examples/vue/basic-use-table/src/main.ts new file mode 100644 index 0000000000..50a4dab0d5 --- /dev/null +++ b/examples/vue/basic-use-table/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import App from './App.vue' +import './index.css' + +createApp(App).mount('#app') diff --git a/examples/vue/basic-use-table/tsconfig.json b/examples/vue/basic-use-table/tsconfig.json new file mode 100644 index 0000000000..500d5a5148 --- /dev/null +++ b/examples/vue/basic-use-table/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/vue/basic-use-table/vite.config.ts b/examples/vue/basic-use-table/vite.config.ts new file mode 100644 index 0000000000..3dec1c870d --- /dev/null +++ b/examples/vue/basic-use-table/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' + +export default defineConfig({ + plugins: [vue(), vueJsx()], +}) diff --git a/examples/vue/basic/.gitignore b/examples/vue/basic/.gitignore deleted file mode 100644 index a547bf36d8..0000000000 --- a/examples/vue/basic/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/examples/vue/basic/index.html b/examples/vue/basic/index.html deleted file mode 100644 index e160e46247..0000000000 --- a/examples/vue/basic/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - Vite App - - - -
- - - diff --git a/examples/vue/basic/package.json b/examples/vue/basic/package.json deleted file mode 100644 index 772c04e8ef..0000000000 --- a/examples/vue/basic/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "tanstack-table-example-vue-basic", - "private": true, - "version": "0.0.0", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview", - "test:types": "vue-tsc" - }, - "dependencies": { - "vue": "^3.4.31", - "@tanstack/vue-table": "^8.20.5" - }, - "devDependencies": { - "@types/node": "^20.14.9", - "@vitejs/plugin-vue": "^5.0.5", - "typescript": "5.4.5", - "vite": "^5.3.2", - "vue-tsc": "^2.0.22" - } -} diff --git a/examples/vue/basic/public/favicon.ico b/examples/vue/basic/public/favicon.ico deleted file mode 100644 index df36fcfb72..0000000000 Binary files a/examples/vue/basic/public/favicon.ico and /dev/null differ diff --git a/examples/vue/basic/src/App.vue b/examples/vue/basic/src/App.vue deleted file mode 100644 index 37bec1c34c..0000000000 --- a/examples/vue/basic/src/App.vue +++ /dev/null @@ -1,191 +0,0 @@ - - - - - diff --git a/examples/vue/basic/src/env.d.ts b/examples/vue/basic/src/env.d.ts deleted file mode 100644 index aafef9509d..0000000000 --- a/examples/vue/basic/src/env.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -/// - -declare module '*.vue' { - import type { DefineComponent } from 'vue' - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types - const component: DefineComponent<{}, {}, any> - export default component -} diff --git a/examples/vue/basic/tsconfig.json b/examples/vue/basic/tsconfig.json deleted file mode 100644 index 0b8e08b465..0000000000 --- a/examples/vue/basic/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "module": "ESNext", - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "preserve", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] -} diff --git a/examples/vue/basic/vite.config.ts b/examples/vue/basic/vite.config.ts deleted file mode 100644 index 05c17402a4..0000000000 --- a/examples/vue/basic/vite.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from 'vite' -import vue from '@vitejs/plugin-vue' - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [vue()], -}) diff --git a/examples/vue/column-groups/README.md b/examples/vue/column-groups/README.md new file mode 100644 index 0000000000..3ac3f1a9b4 --- /dev/null +++ b/examples/vue/column-groups/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run dev` or `yarn dev` diff --git a/examples/vue/column-groups/env.d.ts b/examples/vue/column-groups/env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/vue/column-groups/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/vue/column-groups/index.html b/examples/vue/column-groups/index.html new file mode 100644 index 0000000000..dcd696757f --- /dev/null +++ b/examples/vue/column-groups/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/examples/vue/column-groups/package.json b/examples/vue/column-groups/package.json new file mode 100644 index 0000000000..b0d9adea70 --- /dev/null +++ b/examples/vue/column-groups/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-vue-table-example-column-groups", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint ./src", + "test:types": "vue-tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/vue-table": "^9.0.0-alpha.45", + "vue": "^3.5.34" + }, + "devDependencies": { + "@types/node": "^25.6.2", + "@vitejs/plugin-vue": "^6.0.6", + "@vitejs/plugin-vue-jsx": "^5.1.5", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vue-tsc": "^3.2.8" + } +} diff --git a/examples/vue/column-groups/src/App.tsx b/examples/vue/column-groups/src/App.tsx new file mode 100644 index 0000000000..e51f8c999c --- /dev/null +++ b/examples/vue/column-groups/src/App.tsx @@ -0,0 +1,154 @@ +import { defineComponent, ref } from 'vue' +import { FlexRender, tableFeatures, useTable } from '@tanstack/vue-table' +import { makeData } from './makeData' +import type { + Cell, + ColumnDef, + Header, + HeaderGroup, + Row, +} from '@tanstack/vue-table' +import type { Person } from './makeData' + +const _features = tableFeatures({}) + +const columns: Array> = [ + { + header: 'Name', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'firstName', + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }, + ], + }, + { + header: 'Info', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'age', + header: () => 'Age', + footer: (props) => props.column.id, + }, + { + header: 'More Info', + columns: [ + { + accessorKey: 'visits', + header: () => Visits, + footer: (props) => props.column.id, + }, + { + accessorKey: 'status', + header: 'Status', + footer: (props) => props.column.id, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + footer: (props) => props.column.id, + }, + ], + }, + ], + }, +] + +export default defineComponent({ + name: 'ColumnGroupsExample', + setup() { + const data = ref(makeData(20)) + + const refreshData = () => { + data.value = makeData(20) + } + + const stressTest = () => { + data.value = makeData(1_000) + } + + const table = useTable({ + debugTable: true, + _features, + columns, + get data() { + return data.value + }, + }) + + return () => ( +
+
+ + +
+
+ + + {table + .getHeaderGroups() + .map((headerGroup: HeaderGroup) => ( + + {headerGroup.headers.map( + (header: Header) => ( + + ), + )} + + ))} + + + {table + .getRowModel() + .rows.map((row: Row) => ( + + {row + .getAllCells() + .map((cell: Cell) => ( + + ))} + + ))} + + + {table + .getFooterGroups() + .map((footerGroup: HeaderGroup) => ( + + {footerGroup.headers.map( + (header: Header) => ( + + ), + )} + + ))} + +
+ {header.isPlaceholder ? null : ( + + )} +
+ +
+ {header.isPlaceholder ? null : ( + + )} +
+
+ ) + }, +}) diff --git a/examples/vue/column-groups/src/App.vue b/examples/vue/column-groups/src/App.vue new file mode 100644 index 0000000000..4256c2000a --- /dev/null +++ b/examples/vue/column-groups/src/App.vue @@ -0,0 +1,7 @@ + + + diff --git a/examples/vue/column-groups/src/env.d.ts b/examples/vue/column-groups/src/env.d.ts new file mode 100644 index 0000000000..6977c557dc --- /dev/null +++ b/examples/vue/column-groups/src/env.d.ts @@ -0,0 +1,8 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/examples/vue/column-groups/src/index.css b/examples/vue/column-groups/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/vue/column-groups/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/vue/column-groups/src/main.ts b/examples/vue/column-groups/src/main.ts new file mode 100644 index 0000000000..50a4dab0d5 --- /dev/null +++ b/examples/vue/column-groups/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import App from './App.vue' +import './index.css' + +createApp(App).mount('#app') diff --git a/examples/vue/column-groups/src/makeData.ts b/examples/vue/column-groups/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/vue/column-groups/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/vue/column-groups/tsconfig.json b/examples/vue/column-groups/tsconfig.json new file mode 100644 index 0000000000..500d5a5148 --- /dev/null +++ b/examples/vue/column-groups/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/vue/column-groups/vite.config.ts b/examples/vue/column-groups/vite.config.ts new file mode 100644 index 0000000000..3dec1c870d --- /dev/null +++ b/examples/vue/column-groups/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' + +export default defineConfig({ + plugins: [vue(), vueJsx()], +}) diff --git a/examples/vue/column-ordering/index.html b/examples/vue/column-ordering/index.html index e160e46247..dcd696757f 100644 --- a/examples/vue/column-ordering/index.html +++ b/examples/vue/column-ordering/index.html @@ -5,7 +5,6 @@ Vite App -
diff --git a/examples/vue/column-ordering/package.json b/examples/vue/column-ordering/package.json index 0253cfd353..c74c937159 100644 --- a/examples/vue/column-ordering/package.json +++ b/examples/vue/column-ordering/package.json @@ -1,21 +1,24 @@ { - "name": "tanstack-table-example-vue-column-ordering", - "version": "0.0.0", + "name": "tanstack-vue-table-example-column-ordering", + "private": true, "scripts": { "dev": "vite", "build": "vite build", - "preview": "vite preview" + "preview": "vite preview", + "lint": "eslint ./src", + "test:types": "vue-tsc" }, "dependencies": { - "@faker-js/faker": "^8.4.1", - "vue": "^3.4.31", - "@tanstack/vue-table": "^8.20.5" + "@faker-js/faker": "^10.4.0", + "@tanstack/vue-table": "^9.0.0-alpha.45", + "vue": "^3.5.34" }, "devDependencies": { - "@types/node": "^20.14.9", - "@vitejs/plugin-vue": "^5.0.5", - "typescript": "5.4.5", - "vite": "^5.3.2", - "vue-tsc": "^2.0.22" + "@types/node": "^25.6.2", + "@vitejs/plugin-vue": "^6.0.6", + "@vitejs/plugin-vue-jsx": "^5.1.5", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vue-tsc": "^3.2.8" } } diff --git a/examples/vue/column-ordering/src/App.vue b/examples/vue/column-ordering/src/App.vue index 38b9aa6256..2d110f29fb 100644 --- a/examples/vue/column-ordering/src/App.vue +++ b/examples/vue/column-ordering/src/App.vue @@ -1,121 +1,130 @@ @@ -212,8 +215,9 @@ body { } table { - border: 1px solid lightgray; + border-spacing: 0; border-collapse: collapse; + border: 1px solid lightgray; } tbody { diff --git a/examples/vue/column-ordering/src/env.d.ts b/examples/vue/column-ordering/src/env.d.ts index aafef9509d..6977c557dc 100644 --- a/examples/vue/column-ordering/src/env.d.ts +++ b/examples/vue/column-ordering/src/env.d.ts @@ -2,7 +2,7 @@ declare module '*.vue' { import type { DefineComponent } from 'vue' - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types + const component: DefineComponent<{}, {}, any> export default component } diff --git a/examples/vue/column-ordering/src/index.css b/examples/vue/column-ordering/src/index.css new file mode 100644 index 0000000000..efd0b26417 --- /dev/null +++ b/examples/vue/column-ordering/src/index.css @@ -0,0 +1,374 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td { + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +td:last-child { + border-right: 0; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/vue/column-ordering/src/main.ts b/examples/vue/column-ordering/src/main.ts index 01433bca2a..50a4dab0d5 100644 --- a/examples/vue/column-ordering/src/main.ts +++ b/examples/vue/column-ordering/src/main.ts @@ -1,4 +1,5 @@ import { createApp } from 'vue' import App from './App.vue' +import './index.css' createApp(App).mount('#app') diff --git a/examples/vue/column-ordering/src/makeData.ts b/examples/vue/column-ordering/src/makeData.ts index 1a2576c988..b9055b2d8c 100644 --- a/examples/vue/column-ordering/src/makeData.ts +++ b/examples/vue/column-ordering/src/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,14 +29,14 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! - return range(len).map((): Person => { +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { return { ...newPerson(), subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, diff --git a/examples/vue/column-ordering/tsconfig.json b/examples/vue/column-ordering/tsconfig.json index 0b8e08b465..500d5a5148 100644 --- a/examples/vue/column-ordering/tsconfig.json +++ b/examples/vue/column-ordering/tsconfig.json @@ -5,20 +5,23 @@ "module": "ESNext", "lib": ["ES2020", "DOM", "DOM.Iterable"], "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "preserve", - - /* Linting */ + "jsxImportSource": "vue", "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue", + "vite.config.js", + "vite.config.ts" + ] } diff --git a/examples/vue/column-ordering/vite.config.ts b/examples/vue/column-ordering/vite.config.ts index 05c17402a4..3dec1c870d 100644 --- a/examples/vue/column-ordering/vite.config.ts +++ b/examples/vue/column-ordering/vite.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' -// https://vitejs.dev/config/ export default defineConfig({ - plugins: [vue()], + plugins: [vue(), vueJsx()], }) diff --git a/examples/vue/column-pinning-split/README.md b/examples/vue/column-pinning-split/README.md new file mode 100644 index 0000000000..3ac3f1a9b4 --- /dev/null +++ b/examples/vue/column-pinning-split/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run dev` or `yarn dev` diff --git a/examples/vue/column-pinning-split/env.d.ts b/examples/vue/column-pinning-split/env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/vue/column-pinning-split/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/vue/column-pinning-split/index.html b/examples/vue/column-pinning-split/index.html new file mode 100644 index 0000000000..dcd696757f --- /dev/null +++ b/examples/vue/column-pinning-split/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/examples/vue/column-pinning-split/package.json b/examples/vue/column-pinning-split/package.json new file mode 100644 index 0000000000..b87a77b351 --- /dev/null +++ b/examples/vue/column-pinning-split/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-vue-table-example-column-pinning-split", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint ./src", + "test:types": "vue-tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/vue-table": "^9.0.0-alpha.45", + "vue": "^3.5.34" + }, + "devDependencies": { + "@types/node": "^25.6.2", + "@vitejs/plugin-vue": "^6.0.6", + "@vitejs/plugin-vue-jsx": "^5.1.5", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vue-tsc": "^3.2.8" + } +} diff --git a/examples/vue/column-pinning-split/src/App.vue b/examples/vue/column-pinning-split/src/App.vue new file mode 100644 index 0000000000..3bc13726eb --- /dev/null +++ b/examples/vue/column-pinning-split/src/App.vue @@ -0,0 +1,249 @@ + + + + + diff --git a/examples/vue/column-pinning-split/src/env.d.ts b/examples/vue/column-pinning-split/src/env.d.ts new file mode 100644 index 0000000000..6977c557dc --- /dev/null +++ b/examples/vue/column-pinning-split/src/env.d.ts @@ -0,0 +1,8 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/examples/vue/column-pinning-split/src/index.css b/examples/vue/column-pinning-split/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/vue/column-pinning-split/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/vue/column-pinning-split/src/main.ts b/examples/vue/column-pinning-split/src/main.ts new file mode 100644 index 0000000000..50a4dab0d5 --- /dev/null +++ b/examples/vue/column-pinning-split/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import App from './App.vue' +import './index.css' + +createApp(App).mount('#app') diff --git a/examples/vue/column-pinning-split/src/makeData.ts b/examples/vue/column-pinning-split/src/makeData.ts new file mode 100644 index 0000000000..d274a17f45 --- /dev/null +++ b/examples/vue/column-pinning-split/src/makeData.ts @@ -0,0 +1,47 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + return makeDataLevel() +} diff --git a/examples/vue/column-pinning-split/tsconfig.json b/examples/vue/column-pinning-split/tsconfig.json new file mode 100644 index 0000000000..500d5a5148 --- /dev/null +++ b/examples/vue/column-pinning-split/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/vue/column-pinning-split/vite.config.ts b/examples/vue/column-pinning-split/vite.config.ts new file mode 100644 index 0000000000..3dec1c870d --- /dev/null +++ b/examples/vue/column-pinning-split/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' + +export default defineConfig({ + plugins: [vue(), vueJsx()], +}) diff --git a/examples/vue/column-pinning-sticky/README.md b/examples/vue/column-pinning-sticky/README.md new file mode 100644 index 0000000000..3ac3f1a9b4 --- /dev/null +++ b/examples/vue/column-pinning-sticky/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run dev` or `yarn dev` diff --git a/examples/vue/column-pinning-sticky/env.d.ts b/examples/vue/column-pinning-sticky/env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/vue/column-pinning-sticky/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/vue/column-pinning-sticky/index.html b/examples/vue/column-pinning-sticky/index.html new file mode 100644 index 0000000000..dcd696757f --- /dev/null +++ b/examples/vue/column-pinning-sticky/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/examples/vue/column-pinning-sticky/package.json b/examples/vue/column-pinning-sticky/package.json new file mode 100644 index 0000000000..b34dfa4883 --- /dev/null +++ b/examples/vue/column-pinning-sticky/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-vue-table-example-column-pinning-sticky", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint ./src", + "test:types": "vue-tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/vue-table": "^9.0.0-alpha.45", + "vue": "^3.5.34" + }, + "devDependencies": { + "@types/node": "^25.6.2", + "@vitejs/plugin-vue": "^6.0.6", + "@vitejs/plugin-vue-jsx": "^5.1.5", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vue-tsc": "^3.2.8" + } +} diff --git a/examples/vue/column-pinning-sticky/src/App.vue b/examples/vue/column-pinning-sticky/src/App.vue new file mode 100644 index 0000000000..3bc13726eb --- /dev/null +++ b/examples/vue/column-pinning-sticky/src/App.vue @@ -0,0 +1,249 @@ + + + + + diff --git a/examples/vue/column-pinning-sticky/src/env.d.ts b/examples/vue/column-pinning-sticky/src/env.d.ts new file mode 100644 index 0000000000..6977c557dc --- /dev/null +++ b/examples/vue/column-pinning-sticky/src/env.d.ts @@ -0,0 +1,8 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/examples/vue/column-pinning-sticky/src/index.css b/examples/vue/column-pinning-sticky/src/index.css new file mode 100644 index 0000000000..cf254482e6 --- /dev/null +++ b/examples/vue/column-pinning-sticky/src/index.css @@ -0,0 +1,388 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +.table-container { + border: 1px solid lightgray; + overflow-x: scroll; + width: 100%; + max-width: 960px; + position: relative; + border-collapse: collapse; + border-spacing: 0; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +th { + background-color: lightgray; + border-bottom: 1px solid lightgray; + font-weight: bold; + height: 30px; + padding: 2px 4px; + position: relative; + text-align: center; +} + +td { + background-color: white; + padding: 2px 4px; +} + +.resizer { + background: rgba(0, 0, 0, 0.5); + cursor: col-resize; + height: 100%; + position: absolute; + right: 0; + top: 0; + touch-action: none; + user-select: none; + width: 5px; +} + +.resizer.isResizing { + background: blue; + opacity: 1; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/vue/column-pinning-sticky/src/main.ts b/examples/vue/column-pinning-sticky/src/main.ts new file mode 100644 index 0000000000..50a4dab0d5 --- /dev/null +++ b/examples/vue/column-pinning-sticky/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import App from './App.vue' +import './index.css' + +createApp(App).mount('#app') diff --git a/examples/vue/column-pinning-sticky/src/makeData.ts b/examples/vue/column-pinning-sticky/src/makeData.ts new file mode 100644 index 0000000000..d274a17f45 --- /dev/null +++ b/examples/vue/column-pinning-sticky/src/makeData.ts @@ -0,0 +1,47 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + return makeDataLevel() +} diff --git a/examples/vue/column-pinning-sticky/tsconfig.json b/examples/vue/column-pinning-sticky/tsconfig.json new file mode 100644 index 0000000000..500d5a5148 --- /dev/null +++ b/examples/vue/column-pinning-sticky/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/vue/column-pinning-sticky/vite.config.ts b/examples/vue/column-pinning-sticky/vite.config.ts new file mode 100644 index 0000000000..3dec1c870d --- /dev/null +++ b/examples/vue/column-pinning-sticky/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' + +export default defineConfig({ + plugins: [vue(), vueJsx()], +}) diff --git a/examples/vue/column-pinning/index.html b/examples/vue/column-pinning/index.html index e160e46247..dcd696757f 100644 --- a/examples/vue/column-pinning/index.html +++ b/examples/vue/column-pinning/index.html @@ -5,7 +5,6 @@ Vite App -
diff --git a/examples/vue/column-pinning/package.json b/examples/vue/column-pinning/package.json index 8910bd8d8c..b16034688b 100644 --- a/examples/vue/column-pinning/package.json +++ b/examples/vue/column-pinning/package.json @@ -1,21 +1,24 @@ { - "name": "tanstack-table-example-vue-column-pinning", - "version": "0.0.0", + "name": "tanstack-vue-table-example-column-pinning", + "private": true, "scripts": { "dev": "vite", "build": "vite build", - "preview": "vite preview" + "preview": "vite preview", + "lint": "eslint ./src", + "test:types": "vue-tsc" }, "dependencies": { - "@faker-js/faker": "^8.4.1", - "@tanstack/vue-table": "^8.20.5", - "vue": "^3.4.31" + "@faker-js/faker": "^10.4.0", + "@tanstack/vue-table": "^9.0.0-alpha.45", + "vue": "^3.5.34" }, "devDependencies": { - "@types/node": "^20.14.9", - "@vitejs/plugin-vue": "^5.0.5", - "typescript": "5.4.5", - "vite": "^5.3.2", - "vue-tsc": "^2.0.22" + "@types/node": "^25.6.2", + "@vitejs/plugin-vue": "^6.0.6", + "@vitejs/plugin-vue-jsx": "^5.1.5", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vue-tsc": "^3.2.8" } } diff --git a/examples/vue/column-pinning/src/App.vue b/examples/vue/column-pinning/src/App.vue index f3a64cee67..f421e7080a 100644 --- a/examples/vue/column-pinning/src/App.vue +++ b/examples/vue/column-pinning/src/App.vue @@ -1,136 +1,135 @@ @@ -378,8 +361,9 @@ body { } table { - border: 1px solid lightgray; + border-spacing: 0; border-collapse: collapse; + border: 1px solid lightgray; } tbody { diff --git a/examples/vue/column-pinning/src/env.d.ts b/examples/vue/column-pinning/src/env.d.ts index aafef9509d..6977c557dc 100644 --- a/examples/vue/column-pinning/src/env.d.ts +++ b/examples/vue/column-pinning/src/env.d.ts @@ -2,7 +2,7 @@ declare module '*.vue' { import type { DefineComponent } from 'vue' - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types + const component: DefineComponent<{}, {}, any> export default component } diff --git a/examples/vue/column-pinning/src/index.css b/examples/vue/column-pinning/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/vue/column-pinning/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/vue/column-pinning/src/main.ts b/examples/vue/column-pinning/src/main.ts index 01433bca2a..50a4dab0d5 100644 --- a/examples/vue/column-pinning/src/main.ts +++ b/examples/vue/column-pinning/src/main.ts @@ -1,4 +1,5 @@ import { createApp } from 'vue' import App from './App.vue' +import './index.css' createApp(App).mount('#app') diff --git a/examples/vue/column-pinning/src/makeData.ts b/examples/vue/column-pinning/src/makeData.ts index 1a2576c988..d274a17f45 100644 --- a/examples/vue/column-pinning/src/makeData.ts +++ b/examples/vue/column-pinning/src/makeData.ts @@ -7,11 +7,11 @@ export type Person = { visits: number progress: number status: 'relationship' | 'complicated' | 'single' - subRows?: Person[] + subRows?: Array } const range = (len: number) => { - const arr: number[] = [] + const arr: Array = [] for (let i = 0; i < len; i++) { arr.push(i) } @@ -29,13 +29,13 @@ const newPerson = (): Person => { 'relationship', 'complicated', 'single', - ])[0]!, + ])[0], } } -export function makeData(...lens: number[]) { - const makeDataLevel = (depth = 0): Person[] => { - const len = lens[depth]! +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] return range(len).map((): Person => { return { ...newPerson(), @@ -43,6 +43,5 @@ export function makeData(...lens: number[]) { } }) } - return makeDataLevel() } diff --git a/examples/vue/column-pinning/tsconfig.json b/examples/vue/column-pinning/tsconfig.json index 0b8e08b465..500d5a5148 100644 --- a/examples/vue/column-pinning/tsconfig.json +++ b/examples/vue/column-pinning/tsconfig.json @@ -5,20 +5,23 @@ "module": "ESNext", "lib": ["ES2020", "DOM", "DOM.Iterable"], "skipLibCheck": true, - - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "preserve", - - /* Linting */ + "jsxImportSource": "vue", "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue", + "vite.config.js", + "vite.config.ts" + ] } diff --git a/examples/vue/column-pinning/vite.config.ts b/examples/vue/column-pinning/vite.config.ts index 05c17402a4..3dec1c870d 100644 --- a/examples/vue/column-pinning/vite.config.ts +++ b/examples/vue/column-pinning/vite.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' -// https://vitejs.dev/config/ export default defineConfig({ - plugins: [vue()], + plugins: [vue(), vueJsx()], }) diff --git a/examples/vue/column-resizing-performant/README.md b/examples/vue/column-resizing-performant/README.md new file mode 100644 index 0000000000..3ac3f1a9b4 --- /dev/null +++ b/examples/vue/column-resizing-performant/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run dev` or `yarn dev` diff --git a/examples/vue/column-resizing-performant/env.d.ts b/examples/vue/column-resizing-performant/env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/vue/column-resizing-performant/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/vue/column-resizing-performant/index.html b/examples/vue/column-resizing-performant/index.html new file mode 100644 index 0000000000..dcd696757f --- /dev/null +++ b/examples/vue/column-resizing-performant/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/examples/vue/column-resizing-performant/package.json b/examples/vue/column-resizing-performant/package.json new file mode 100644 index 0000000000..1d9570fb86 --- /dev/null +++ b/examples/vue/column-resizing-performant/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-vue-table-example-column-resizing-performant", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint ./src", + "test:types": "vue-tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/vue-table": "^9.0.0-alpha.45", + "vue": "^3.5.34" + }, + "devDependencies": { + "@types/node": "^25.6.2", + "@vitejs/plugin-vue": "^6.0.6", + "@vitejs/plugin-vue-jsx": "^5.1.5", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vue-tsc": "^3.2.8" + } +} diff --git a/examples/vue/column-resizing-performant/src/App.vue b/examples/vue/column-resizing-performant/src/App.vue new file mode 100644 index 0000000000..9a33ddfeb3 --- /dev/null +++ b/examples/vue/column-resizing-performant/src/App.vue @@ -0,0 +1,249 @@ + + + + + diff --git a/examples/vue/column-resizing-performant/src/env.d.ts b/examples/vue/column-resizing-performant/src/env.d.ts new file mode 100644 index 0000000000..6977c557dc --- /dev/null +++ b/examples/vue/column-resizing-performant/src/env.d.ts @@ -0,0 +1,8 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/examples/vue/column-resizing-performant/src/index.css b/examples/vue/column-resizing-performant/src/index.css new file mode 100644 index 0000000000..78a395f530 --- /dev/null +++ b/examples/vue/column-resizing-performant/src/index.css @@ -0,0 +1,411 @@ +* { + box-sizing: border-box; +} + +html { + font-family: sans-serif; + font-size: 14px; +} + +table, +.divTable { + border: 1px solid lightgray; + width: fit-content; + border-collapse: collapse; + border-spacing: 0; +} + +.tr { + display: flex; +} + +tr, +.tr { + width: fit-content; + height: 30px; +} + +th, +.th, +td, +.td { + box-shadow: inset 0 0 0 1px lightgray; + padding: 0.25rem; +} + +th, +.th { + padding: 2px 4px; + position: relative; + font-weight: bold; + text-align: center; + height: 30px; +} + +td, +.td { + height: 30px; +} + +.resizer { + position: absolute; + top: 0; + height: 100%; + width: 5px; + background: rgba(0, 0, 0, 0.5); + cursor: col-resize; + user-select: none; + touch-action: none; +} + +.resizer.isResizing { + background: blue; + opacity: 1; +} + +@media (hover: hover) { + .resizer { + opacity: 0; + } + + *:hover > .resizer { + opacity: 1; + } +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/vue/column-resizing-performant/src/main.ts b/examples/vue/column-resizing-performant/src/main.ts new file mode 100644 index 0000000000..50a4dab0d5 --- /dev/null +++ b/examples/vue/column-resizing-performant/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import App from './App.vue' +import './index.css' + +createApp(App).mount('#app') diff --git a/examples/vue/column-resizing-performant/src/makeData.ts b/examples/vue/column-resizing-performant/src/makeData.ts new file mode 100644 index 0000000000..d63c724b74 --- /dev/null +++ b/examples/vue/column-resizing-performant/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/vue/column-resizing-performant/tsconfig.json b/examples/vue/column-resizing-performant/tsconfig.json new file mode 100644 index 0000000000..500d5a5148 --- /dev/null +++ b/examples/vue/column-resizing-performant/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/vue/column-resizing-performant/vite.config.ts b/examples/vue/column-resizing-performant/vite.config.ts new file mode 100644 index 0000000000..3dec1c870d --- /dev/null +++ b/examples/vue/column-resizing-performant/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' + +export default defineConfig({ + plugins: [vue(), vueJsx()], +}) diff --git a/examples/vue/column-resizing/README.md b/examples/vue/column-resizing/README.md new file mode 100644 index 0000000000..3ac3f1a9b4 --- /dev/null +++ b/examples/vue/column-resizing/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run dev` or `yarn dev` diff --git a/examples/vue/column-resizing/env.d.ts b/examples/vue/column-resizing/env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/vue/column-resizing/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/vue/column-resizing/index.html b/examples/vue/column-resizing/index.html new file mode 100644 index 0000000000..dcd696757f --- /dev/null +++ b/examples/vue/column-resizing/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/examples/vue/column-resizing/package.json b/examples/vue/column-resizing/package.json new file mode 100644 index 0000000000..7d4c9f5bd3 --- /dev/null +++ b/examples/vue/column-resizing/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-vue-table-example-column-resizing", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint ./src", + "test:types": "vue-tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/vue-table": "^9.0.0-alpha.45", + "vue": "^3.5.34" + }, + "devDependencies": { + "@types/node": "^25.6.2", + "@vitejs/plugin-vue": "^6.0.6", + "@vitejs/plugin-vue-jsx": "^5.1.5", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vue-tsc": "^3.2.8" + } +} diff --git a/examples/vue/column-resizing/src/App.vue b/examples/vue/column-resizing/src/App.vue new file mode 100644 index 0000000000..3bc13726eb --- /dev/null +++ b/examples/vue/column-resizing/src/App.vue @@ -0,0 +1,249 @@ + + + + + diff --git a/examples/vue/column-resizing/src/env.d.ts b/examples/vue/column-resizing/src/env.d.ts new file mode 100644 index 0000000000..6977c557dc --- /dev/null +++ b/examples/vue/column-resizing/src/env.d.ts @@ -0,0 +1,8 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/examples/vue/column-resizing/src/index.css b/examples/vue/column-resizing/src/index.css new file mode 100644 index 0000000000..d7f648b224 --- /dev/null +++ b/examples/vue/column-resizing/src/index.css @@ -0,0 +1,419 @@ +* { + box-sizing: border-box; +} + +html { + font-family: sans-serif; + font-size: 14px; +} + +table, +.divTable { + border: 1px solid lightgray; + width: fit-content; + border-collapse: collapse; + border-spacing: 0; +} + +.tr { + display: flex; +} + +tr, +.tr { + width: fit-content; + height: 30px; +} + +th, +.th, +td, +.td { + box-shadow: inset 0 0 0 1px lightgray; + padding: 0.25rem; +} + +th, +.th { + padding: 2px 4px; + position: relative; + font-weight: bold; + text-align: center; + height: 30px; +} + +td, +.td { + height: 30px; +} + +.resizer { + position: absolute; + top: 0; + height: 100%; + width: 5px; + background: rgba(0, 0, 0, 0.5); + cursor: col-resize; + user-select: none; + touch-action: none; +} + +.resizer.ltr { + right: 0; +} + +.resizer.rtl { + left: 0; +} + +.resizer.isResizing { + background: blue; + opacity: 1; +} + +@media (hover: hover) { + .resizer { + opacity: 0; + } + + *:hover > .resizer { + opacity: 1; + } +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/vue/column-resizing/src/main.ts b/examples/vue/column-resizing/src/main.ts new file mode 100644 index 0000000000..50a4dab0d5 --- /dev/null +++ b/examples/vue/column-resizing/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import App from './App.vue' +import './index.css' + +createApp(App).mount('#app') diff --git a/examples/vue/column-resizing/src/makeData.ts b/examples/vue/column-resizing/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/vue/column-resizing/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/vue/column-resizing/tsconfig.json b/examples/vue/column-resizing/tsconfig.json new file mode 100644 index 0000000000..500d5a5148 --- /dev/null +++ b/examples/vue/column-resizing/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/vue/column-resizing/vite.config.ts b/examples/vue/column-resizing/vite.config.ts new file mode 100644 index 0000000000..3dec1c870d --- /dev/null +++ b/examples/vue/column-resizing/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' + +export default defineConfig({ + plugins: [vue(), vueJsx()], +}) diff --git a/examples/vue/column-sizing/README.md b/examples/vue/column-sizing/README.md new file mode 100644 index 0000000000..3ac3f1a9b4 --- /dev/null +++ b/examples/vue/column-sizing/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run dev` or `yarn dev` diff --git a/examples/vue/column-sizing/env.d.ts b/examples/vue/column-sizing/env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/vue/column-sizing/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/vue/column-sizing/index.html b/examples/vue/column-sizing/index.html new file mode 100644 index 0000000000..dcd696757f --- /dev/null +++ b/examples/vue/column-sizing/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/examples/vue/column-sizing/package.json b/examples/vue/column-sizing/package.json new file mode 100644 index 0000000000..c31663b9e9 --- /dev/null +++ b/examples/vue/column-sizing/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-vue-table-example-column-sizing", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint ./src", + "test:types": "vue-tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/vue-table": "^9.0.0-alpha.45", + "vue": "^3.5.34" + }, + "devDependencies": { + "@types/node": "^25.6.2", + "@vitejs/plugin-vue": "^6.0.6", + "@vitejs/plugin-vue-jsx": "^5.1.5", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vue-tsc": "^3.2.8" + } +} diff --git a/examples/vue/column-sizing/src/App.vue b/examples/vue/column-sizing/src/App.vue new file mode 100644 index 0000000000..3bc13726eb --- /dev/null +++ b/examples/vue/column-sizing/src/App.vue @@ -0,0 +1,249 @@ + + + + + diff --git a/examples/vue/column-sizing/src/env.d.ts b/examples/vue/column-sizing/src/env.d.ts new file mode 100644 index 0000000000..6977c557dc --- /dev/null +++ b/examples/vue/column-sizing/src/env.d.ts @@ -0,0 +1,8 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/examples/vue/column-sizing/src/index.css b/examples/vue/column-sizing/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/vue/column-sizing/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/vue/column-sizing/src/main.ts b/examples/vue/column-sizing/src/main.ts new file mode 100644 index 0000000000..50a4dab0d5 --- /dev/null +++ b/examples/vue/column-sizing/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import App from './App.vue' +import './index.css' + +createApp(App).mount('#app') diff --git a/examples/vue/column-sizing/src/makeData.ts b/examples/vue/column-sizing/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/vue/column-sizing/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/vue/column-sizing/tsconfig.json b/examples/vue/column-sizing/tsconfig.json new file mode 100644 index 0000000000..500d5a5148 --- /dev/null +++ b/examples/vue/column-sizing/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/vue/column-sizing/vite.config.ts b/examples/vue/column-sizing/vite.config.ts new file mode 100644 index 0000000000..3dec1c870d --- /dev/null +++ b/examples/vue/column-sizing/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' + +export default defineConfig({ + plugins: [vue(), vueJsx()], +}) diff --git a/examples/vue/column-visibility/README.md b/examples/vue/column-visibility/README.md new file mode 100644 index 0000000000..3ac3f1a9b4 --- /dev/null +++ b/examples/vue/column-visibility/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run dev` or `yarn dev` diff --git a/examples/vue/column-visibility/env.d.ts b/examples/vue/column-visibility/env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/vue/column-visibility/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/vue/column-visibility/index.html b/examples/vue/column-visibility/index.html new file mode 100644 index 0000000000..dcd696757f --- /dev/null +++ b/examples/vue/column-visibility/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/examples/vue/column-visibility/package.json b/examples/vue/column-visibility/package.json new file mode 100644 index 0000000000..5e8d07b9cf --- /dev/null +++ b/examples/vue/column-visibility/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-vue-table-example-column-visibility", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint ./src", + "test:types": "vue-tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/vue-table": "^9.0.0-alpha.45", + "vue": "^3.5.34" + }, + "devDependencies": { + "@types/node": "^25.6.2", + "@vitejs/plugin-vue": "^6.0.6", + "@vitejs/plugin-vue-jsx": "^5.1.5", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vue-tsc": "^3.2.8" + } +} diff --git a/examples/vue/column-visibility/src/App.tsx b/examples/vue/column-visibility/src/App.tsx new file mode 100644 index 0000000000..0d400685be --- /dev/null +++ b/examples/vue/column-visibility/src/App.tsx @@ -0,0 +1,189 @@ +import { defineComponent, ref } from 'vue' +import { + FlexRender, + columnVisibilityFeature, + tableFeatures, + useTable, +} from '@tanstack/vue-table' +import { makeData } from './makeData' +import type { + Cell, + Column, + ColumnDef, + Header, + HeaderGroup, + Row, +} from '@tanstack/vue-table' +import type { Person } from './makeData' + +const _features = tableFeatures({ columnVisibilityFeature }) + +const columns: Array> = [ + { + header: 'Name', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'firstName', + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }, + ], + }, + { + header: 'Info', + footer: (props) => props.column.id, + columns: [ + { + accessorKey: 'age', + header: () => 'Age', + footer: (props) => props.column.id, + }, + { + header: 'More Info', + columns: [ + { + accessorKey: 'visits', + header: () => Visits, + footer: (props) => props.column.id, + }, + { + accessorKey: 'status', + header: 'Status', + footer: (props) => props.column.id, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + footer: (props) => props.column.id, + }, + ], + }, + ], + }, +] + +export default defineComponent({ + name: 'ColumnVisibilityExample', + setup() { + const data = ref(makeData(20)) + + const refreshData = () => { + data.value = makeData(20) + } + + const stressTest = () => { + data.value = makeData(1_000) + } + + const table = useTable({ + debugTable: true, + _features, + columns, + get data() { + return data.value + }, + }) + + return () => ( +
+
+ + +
+
+
+
+ +
+ {table + .getAllLeafColumns() + .map((column: Column) => ( +
+ +
+ ))} +
+
+ + + {table + .getHeaderGroups() + .map((headerGroup: HeaderGroup) => ( + + {headerGroup.headers.map( + (header: Header) => ( + + ), + )} + + ))} + + + {table + .getRowModel() + .rows.map((row: Row) => ( + + {row + .getVisibleCells() + .map((cell: Cell) => ( + + ))} + + ))} + + + {table + .getFooterGroups() + .map((footerGroup: HeaderGroup) => ( + + {footerGroup.headers.map( + (header: Header) => ( + + ), + )} + + ))} + +
+ {header.isPlaceholder ? null : ( + + )} +
+ +
+ {header.isPlaceholder ? null : ( + + )} +
+
+
{JSON.stringify(table.store.state, null, 2)}
+
+ ) + }, +}) diff --git a/examples/vue/column-visibility/src/App.vue b/examples/vue/column-visibility/src/App.vue new file mode 100644 index 0000000000..4256c2000a --- /dev/null +++ b/examples/vue/column-visibility/src/App.vue @@ -0,0 +1,7 @@ + + + diff --git a/examples/vue/column-visibility/src/env.d.ts b/examples/vue/column-visibility/src/env.d.ts new file mode 100644 index 0000000000..6977c557dc --- /dev/null +++ b/examples/vue/column-visibility/src/env.d.ts @@ -0,0 +1,8 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/examples/vue/column-visibility/src/index.css b/examples/vue/column-visibility/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/vue/column-visibility/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/vue/column-visibility/src/main.ts b/examples/vue/column-visibility/src/main.ts new file mode 100644 index 0000000000..50a4dab0d5 --- /dev/null +++ b/examples/vue/column-visibility/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import App from './App.vue' +import './index.css' + +createApp(App).mount('#app') diff --git a/examples/vue/column-visibility/src/makeData.ts b/examples/vue/column-visibility/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/vue/column-visibility/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/vue/column-visibility/tsconfig.json b/examples/vue/column-visibility/tsconfig.json new file mode 100644 index 0000000000..500d5a5148 --- /dev/null +++ b/examples/vue/column-visibility/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/vue/column-visibility/vite.config.ts b/examples/vue/column-visibility/vite.config.ts new file mode 100644 index 0000000000..3dec1c870d --- /dev/null +++ b/examples/vue/column-visibility/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' + +export default defineConfig({ + plugins: [vue(), vueJsx()], +}) diff --git a/examples/vue/composable-tables/README.md b/examples/vue/composable-tables/README.md new file mode 100644 index 0000000000..3ac3f1a9b4 --- /dev/null +++ b/examples/vue/composable-tables/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run dev` or `yarn dev` diff --git a/examples/vue/composable-tables/index.html b/examples/vue/composable-tables/index.html new file mode 100644 index 0000000000..864634b6a4 --- /dev/null +++ b/examples/vue/composable-tables/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Table Vue Composable Tables + + +
+ + + diff --git a/examples/vue/composable-tables/package.json b/examples/vue/composable-tables/package.json new file mode 100644 index 0000000000..3e27897f63 --- /dev/null +++ b/examples/vue/composable-tables/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-vue-table-example-composable-tables", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint ./src", + "test:types": "vue-tsc" + }, + "dependencies": { + "@tanstack/vue-store": "^0.11.0", + "@tanstack/vue-table": "^9.0.0-alpha.45", + "vue": "^3.5.34" + }, + "devDependencies": { + "@types/node": "^25.6.2", + "@vitejs/plugin-vue": "^6.0.6", + "@vitejs/plugin-vue-jsx": "^5.1.5", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vue-tsc": "^3.2.8" + } +} diff --git a/examples/vue/composable-tables/src/App.vue b/examples/vue/composable-tables/src/App.vue new file mode 100644 index 0000000000..69caa718d9 --- /dev/null +++ b/examples/vue/composable-tables/src/App.vue @@ -0,0 +1,11 @@ + + + diff --git a/examples/vue/composable-tables/src/components/ProductsTable.vue b/examples/vue/composable-tables/src/components/ProductsTable.vue new file mode 100644 index 0000000000..362f1ffb0b --- /dev/null +++ b/examples/vue/composable-tables/src/components/ProductsTable.vue @@ -0,0 +1,144 @@ + + + diff --git a/examples/vue/composable-tables/src/components/UsersTable.vue b/examples/vue/composable-tables/src/components/UsersTable.vue new file mode 100644 index 0000000000..c876473efc --- /dev/null +++ b/examples/vue/composable-tables/src/components/UsersTable.vue @@ -0,0 +1,172 @@ + + + diff --git a/examples/vue/composable-tables/src/components/cell-components.ts b/examples/vue/composable-tables/src/components/cell-components.ts new file mode 100644 index 0000000000..bacd92252e --- /dev/null +++ b/examples/vue/composable-tables/src/components/cell-components.ts @@ -0,0 +1,98 @@ +import { defineComponent, h } from 'vue' +import { useCellContext } from '../hooks/table' + +export const TextCell = defineComponent({ + name: 'TextCell', + setup() { + const cell = useCellContext() + return () => h('span', String(cell.getValue() ?? '')) + }, +}) + +export const NumberCell = defineComponent({ + name: 'NumberCell', + setup() { + const cell = useCellContext() + return () => h('span', Number(cell.getValue() ?? 0).toLocaleString()) + }, +}) + +export const StatusCell = defineComponent({ + name: 'StatusCell', + setup() { + const cell = useCellContext() + return () => { + const status = String(cell.getValue() ?? '') + const statusClass = + status === 'Single' + ? 'single' + : status === 'Complicated' + ? 'complicated' + : 'relationship' + + return h('span', { class: ['status-pill', statusClass] }, status) + } + }, +}) + +export const ProgressCell = defineComponent({ + name: 'ProgressCell', + setup() { + const cell = useCellContext() + return () => { + const value = Number(cell.getValue() ?? 0) + + return h('div', { class: 'progress-track' }, [ + h('div', { + class: 'progress-bar', + style: { width: `${value}%` }, + }), + ]) + } + }, +}) + +export const RowActionsCell = defineComponent({ + name: 'RowActionsCell', + setup() { + const cell = useCellContext() + + return () => + h( + 'button', + { + onClick: () => { + const row = cell.row.original as { + firstName?: string + name?: string + } + window.alert(`Selected ${row.firstName ?? row.name ?? 'row'}`) + }, + }, + 'Open', + ) + }, +}) + +export const PriceCell = defineComponent({ + name: 'PriceCell', + setup() { + const cell = useCellContext() + return () => + h( + 'span', + `$${Number(cell.getValue() ?? 0).toLocaleString(undefined, { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })}`, + ) + }, +}) + +export const CategoryCell = defineComponent({ + name: 'CategoryCell', + setup() { + const cell = useCellContext() + return () => h('span', { class: 'muted' }, String(cell.getValue() ?? '')) + }, +}) diff --git a/examples/vue/composable-tables/src/components/header-components.ts b/examples/vue/composable-tables/src/components/header-components.ts new file mode 100644 index 0000000000..5cb64d8667 --- /dev/null +++ b/examples/vue/composable-tables/src/components/header-components.ts @@ -0,0 +1,75 @@ +import { computed, defineComponent, h } from 'vue' +import { useHeaderContext } from '../hooks/table' + +export const SortIndicator = defineComponent({ + name: 'SortIndicator', + setup() { + const header = useHeaderContext() + + return () => { + const sorted = header.column.getIsSorted() + const text = sorted === 'asc' ? '▲' : sorted === 'desc' ? '▼' : '' + return text ? h('span', { class: 'sort-indicator' }, text) : null + } + }, +}) + +export const ColumnFilter = defineComponent({ + name: 'ColumnFilter', + setup() { + const header = useHeaderContext() + const value = computed( + () => (header.column.getFilterValue() as string | undefined) ?? '', + ) + + return () => { + if (!header.column.getCanFilter()) { + return null + } + + return h( + 'div', + { + class: 'column-filter', + onClick: (event: Event) => event.stopPropagation(), + }, + [ + h('input', { + type: 'text', + value: value.value, + placeholder: `Filter ${header.column.id}...`, + onInput: (event: Event) => + header.column.setFilterValue( + (event.target as HTMLInputElement).value, + ), + }), + ], + ) + } + }, +}) + +export const FooterColumnId = defineComponent({ + name: 'FooterColumnId', + setup() { + const header = useHeaderContext() + return () => h('span', header.column.id) + }, +}) + +export const FooterSum = defineComponent({ + name: 'FooterSum', + setup() { + const header = useHeaderContext() + + return () => { + const rows = header.getContext().table.getFilteredRowModel().rows + const sum = rows.reduce((total, row) => { + const value = row.getValue(header.column.id) + return total + (typeof value === 'number' ? value : 0) + }, 0) + + return h('span', `Sum: ${sum.toLocaleString()}`) + } + }, +}) diff --git a/examples/vue/composable-tables/src/components/table-components.ts b/examples/vue/composable-tables/src/components/table-components.ts new file mode 100644 index 0000000000..750869c0db --- /dev/null +++ b/examples/vue/composable-tables/src/components/table-components.ts @@ -0,0 +1,159 @@ +import { defineComponent, h } from 'vue' +import { useSelector } from '@tanstack/vue-store' +import { useTableContext } from '../hooks/table' +import type { PropType } from 'vue' + +export const PaginationControls = defineComponent({ + name: 'PaginationControls', + setup() { + const table = useTableContext() + const pagination = useSelector( + table.store, + (state: ReturnType) => state.pagination, + ) + + return () => + h('div', { class: 'pagination' }, [ + h( + 'button', + { + onClick: () => table.firstPage(), + disabled: !table.getCanPreviousPage(), + }, + '<<', + ), + h( + 'button', + { + disabled: !table.getCanPreviousPage(), + onClick: () => table.previousPage(), + }, + '<', + ), + h( + 'button', + { + disabled: !table.getCanNextPage(), + onClick: () => table.nextPage(), + }, + '>', + ), + h( + 'button', + { + onClick: () => table.lastPage(), + disabled: !table.getCanNextPage(), + }, + '>>', + ), + h('span', [ + 'Page ', + h( + 'strong', + `${(pagination.value.pageIndex + 1).toLocaleString()} of ${table.getPageCount().toLocaleString()}`, + ), + ]), + h('span', [ + '| Go to page:', + h('input', { + type: 'number', + min: 1, + max: table.getPageCount(), + value: pagination.value.pageIndex + 1, + onChange: (event: Event) => { + const value = (event.target as HTMLInputElement).value + const page = value ? Number(value) - 1 : 0 + table.setPageIndex(page) + }, + }), + ]), + h( + 'select', + { + value: pagination.value.pageSize, + onChange: (event: Event) => { + table.setPageSize( + Number((event.target as HTMLSelectElement).value), + ) + }, + }, + [10, 20, 30, 40, 50].map((pageSize) => + h('option', { key: pageSize, value: pageSize }, `Show ${pageSize}`), + ), + ), + ]) + }, +}) + +export const RowCount = defineComponent({ + name: 'RowCount', + setup() { + const table = useTableContext() + const stateSnapshot = useSelector( + table.store, + (state: ReturnType) => ({ + pagination: state.pagination, + columnFilters: state.columnFilters, + globalFilter: state.globalFilter, + sorting: state.sorting, + }), + ) + + return () => { + stateSnapshot.value + + return h( + 'div', + { class: 'row-count' }, + `Showing ${table.getRowModel().rows.length.toLocaleString()} of ${table.getRowCount().toLocaleString()} rows`, + ) + } + }, +}) + +export const TableToolbar = defineComponent({ + name: 'TableToolbar', + props: { + title: { + type: String, + required: true, + }, + onRefresh: { + type: Function as PropType<() => void>, + default: undefined, + }, + onStressTest: { + type: Function as PropType<() => void>, + default: undefined, + }, + }, + setup(props) { + const table = useTableContext() + return () => + h('div', { class: 'table-toolbar' }, [ + h('h2', props.title), + h('div', { class: 'table-toolbar-actions' }, [ + props.onRefresh + ? h( + 'button', + { onClick: () => props.onRefresh?.() }, + 'Regenerate Data', + ) + : null, + props.onStressTest + ? h( + 'button', + { onClick: () => props.onStressTest?.() }, + 'Stress Test (200k rows)', + ) + : null, + h( + 'button', + { onClick: () => table.resetColumnFilters() }, + 'Clear Filters', + ), + h('button', { onClick: () => table.resetSorting() }, 'Clear Sorting'), + ]), + ]) + }, +}) diff --git a/examples/vue/composable-tables/src/env.d.ts b/examples/vue/composable-tables/src/env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/vue/composable-tables/src/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/vue/composable-tables/src/hooks/table.ts b/examples/vue/composable-tables/src/hooks/table.ts new file mode 100644 index 0000000000..a17022f5a7 --- /dev/null +++ b/examples/vue/composable-tables/src/hooks/table.ts @@ -0,0 +1,104 @@ +import { + columnFilteringFeature, + createFilteredRowModel, + createPaginatedRowModel, + createSortedRowModel, + createTableHook, + filterFns, + globalFilteringFeature, + rowPaginationFeature, + rowSortingFeature, + sortFns, + tableFeatures, +} from '@tanstack/vue-table' +import { + CategoryCell, + NumberCell, + PriceCell, + ProgressCell, + RowActionsCell, + StatusCell, + TextCell, +} from '../components/cell-components' +import { + ColumnFilter, + FooterColumnId, + FooterSum, + SortIndicator, +} from '../components/header-components' +import { + PaginationControls, + RowCount, + TableToolbar, +} from '../components/table-components' +import type { + Cell, + CellData, + Header, + RowData, + VueTable, +} from '@tanstack/vue-table' + +// Define features separately to extract the type for explicit annotations below. +// This is needed to break the circular inference chain caused by the component +// files (table-components, cell-components, header-components) importing context +// functions from this file, while this file imports from those component files. +const _features = tableFeatures({ + columnFilteringFeature, + globalFilteringFeature, + rowPaginationFeature, + rowSortingFeature, +}) + +const _hook = createTableHook({ + _features, + _rowModels: { + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, + getRowId: (row) => row.id, + tableComponents: { + PaginationControls, + RowCount, + TableToolbar, + }, + cellComponents: { + TextCell, + NumberCell, + StatusCell, + ProgressCell, + RowActionsCell, + PriceCell, + CategoryCell, + }, + headerComponents: { + SortIndicator, + ColumnFilter, + FooterColumnId, + FooterSum, + }, +}) + +export const createAppColumnHelper = _hook.createAppColumnHelper +export const useAppTable = _hook.useAppTable + +// Explicit type annotations break the circular inference chain (TS7022). +// TypeScript cannot infer the types of these when the component files that +// import them are also imported by this module (circular dependency). +export const useTableContext: () => VueTable< + typeof _features, + TData +> = _hook.useTableContext + +export const useCellContext: () => Cell< + typeof _features, + any, + TValue +> = _hook.useCellContext + +export const useHeaderContext: () => Header< + typeof _features, + any, + TValue +> = _hook.useHeaderContext diff --git a/examples/vue/composable-tables/src/index.css b/examples/vue/composable-tables/src/index.css new file mode 100644 index 0000000000..4d8fef0142 --- /dev/null +++ b/examples/vue/composable-tables/src/index.css @@ -0,0 +1,605 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + width: 100%; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th, +td { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 8px 12px; + text-align: left; +} + +th { + background-color: #f5f5f5; + font-weight: 600; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +.table-container { + padding: 16px; + max-width: 1200px; + margin: 0 auto; + border-collapse: collapse; + border-spacing: 0; +} + +.pagination { + display: flex; + align-items: center; + gap: 8px; + margin-top: 16px; + flex-wrap: wrap; +} + +.pagination button { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px 8px; + cursor: pointer; + background: white; +} + +.pagination button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.pagination input { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px; + width: 64px; +} + +.pagination select { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px; +} + +.sort-indicator { + margin-left: 4px; +} + +.column-filter { + margin-top: 4px; +} + +.column-filter input { + border: 1px solid #ccc; + border-radius: 4px; + padding: 4px; + width: 100%; + font-size: 12px; +} + +.sortable-header { + cursor: pointer; + user-select: none; +} + +.sortable-header:hover { + background-color: #e8e8e8; +} + +.row-actions { + display: flex; + gap: 4px; +} + +.row-actions button { + border: 1px solid #ccc; + border-radius: 4px; + padding: 2px 8px; + cursor: pointer; + background: white; + font-size: 12px; +} + +.row-actions button:hover { + background-color: #f0f0f0; +} + +.status-badge { + display: inline-block; + padding: 2px 8px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; +} + +.status-badge.relationship { + background-color: #d4edda; + color: #155724; +} + +.status-badge.complicated { + background-color: #fff3cd; + color: #856404; +} + +.status-badge.single { + background-color: #cce5ff; + color: #004085; +} + +.progress-bar { + width: 100%; + height: 8px; + background-color: #e9ecef; + border-radius: 4px; + overflow: hidden; +} + +.progress-bar-fill { + height: 100%; + background-color: #007bff; + transition: width 0.2s; +} + +.table-toolbar { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + flex-wrap: wrap; + gap: 8px; + border-collapse: collapse; + border-spacing: 0; +} + +.table-toolbar h2 { + margin: 0; + border-collapse: collapse; + border-spacing: 0; +} + +.table-toolbar-actions { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.table-toolbar button { + border: 1px solid #ccc; + border-radius: 4px; + padding: 8px 16px; + cursor: pointer; + background: white; + border-collapse: collapse; + border-spacing: 0; +} + +.table-toolbar button:hover { + background-color: #f0f0f0; + border-collapse: collapse; + border-spacing: 0; +} + +.row-count { + color: #666; + font-size: 14px; + margin-top: 8px; +} + +.app { + padding: 16px; +} + +.app h1 { + text-align: center; + margin-bottom: 8px; +} + +.description { + text-align: center; + color: #666; + margin-bottom: 32px; +} + +.description code { + background-color: #f5f5f5; + padding: 2px 6px; + border-radius: 4px; + font-size: 13px; +} + +.table-divider { + height: 48px; + border-bottom: 1px solid #e0e0e0; + margin: 32px auto; + max-width: 1200px; + border-collapse: collapse; + border-spacing: 0; +} + +.category-badge { + display: inline-block; + padding: 2px 8px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; + text-transform: capitalize; +} + +.category-badge.electronics { + background-color: #e3f2fd; + color: #1565c0; +} + +.category-badge.clothing { + background-color: #fce4ec; + color: #c2185b; +} + +.category-badge.food { + background-color: #e8f5e9; + color: #2e7d32; +} + +.category-badge.books { + background-color: #fff8e1; + color: #f57c00; +} + +.price { + font-weight: 600; + color: #2e7d32; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/vue/composable-tables/src/main.ts b/examples/vue/composable-tables/src/main.ts new file mode 100644 index 0000000000..50a4dab0d5 --- /dev/null +++ b/examples/vue/composable-tables/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import App from './App.vue' +import './index.css' + +createApp(App).mount('#app') diff --git a/examples/vue/composable-tables/src/makeData.ts b/examples/vue/composable-tables/src/makeData.ts new file mode 100644 index 0000000000..17dec1c6de --- /dev/null +++ b/examples/vue/composable-tables/src/makeData.ts @@ -0,0 +1,77 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +export type Product = { + id: string + name: string + category: 'electronics' | 'clothing' | 'food' | 'books' + price: number + stock: number + rating: number +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +const newProduct = (): Product => { + return { + id: faker.string.uuid(), + name: faker.commerce.productName(), + category: faker.helpers.shuffle([ + 'electronics', + 'clothing', + 'food', + 'books', + ])[0], + price: parseFloat(faker.commerce.price({ min: 5, max: 500 })), + stock: faker.number.int({ min: 0, max: 200 }), + rating: faker.number.int({ min: 0, max: 100 }), + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} + +export function makeProductData(count: number): Array { + return range(count).map(() => newProduct()) +} diff --git a/examples/vue/composable-tables/src/styles.css b/examples/vue/composable-tables/src/styles.css new file mode 100644 index 0000000000..4b832a2b14 --- /dev/null +++ b/examples/vue/composable-tables/src/styles.css @@ -0,0 +1,488 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +body { + margin: 0; + background: #f6f7fb; + color: #1f2937; +} + +* { + box-sizing: border-box; +} + +#app { + padding: 24px; +} + +.page { + display: grid; + gap: 24px; +} + +.table-container { + display: grid; + gap: 12px; + padding: 16px; + border: 1px solid #d0d7de; + border-radius: 12px; + background: white; + box-shadow: 0 4px 14px rgb(15 23 42 / 6%); + border-collapse: collapse; + border-spacing: 0; +} + +.toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.toolbar-actions { + display: flex; + align-items: center; + gap: 8px; +} + +.status-pill { + display: inline-flex; + padding: 2px 8px; + border-radius: 999px; + font-size: 12px; + background: #e5e7eb; +} + +.status-pill.single { + background: #dbeafe; +} + +.status-pill.complicated { + background: #fee2e2; +} + +.status-pill.relationship { + background: #dcfce7; +} + +.progress-track { + width: 100%; + max-width: 160px; + height: 8px; + border-radius: 999px; + background: #e5e7eb; + overflow: hidden; +} + +.progress-bar { + height: 100%; + background: #2563eb; +} + +.table-element { + width: 100%; + border-collapse: collapse; + border-spacing: 0; +} + +.table-element th, +.table-element td { + padding: 8px 10px; + border-bottom: 1px solid #e5e7eb; + text-align: left; + vertical-align: top; + border-collapse: collapse; + border-spacing: 0; +} + +.table-element tfoot td { + color: #6b7280; + font-size: 12px; + border-collapse: collapse; + border-spacing: 0; +} + +.sortable-header { + cursor: pointer; + user-select: none; +} + +.sort-indicator { + margin-left: 6px; + font-size: 12px; +} + +.filter-input, +.toolbar-input { + width: 100%; + padding: 6px 8px; + border: 1px solid #cbd5e1; + border-radius: 8px; +} + +.toolbar-input { + min-width: 220px; +} + +.pager { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +} + +.muted { + color: #6b7280; +} + +button { + padding: 6px 10px; + border: 1px solid #cbd5e1; + border-radius: 8px; + background: white; + cursor: pointer; +} + +button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/vue/composable-tables/tsconfig.json b/examples/vue/composable-tables/tsconfig.json new file mode 100644 index 0000000000..500d5a5148 --- /dev/null +++ b/examples/vue/composable-tables/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/vue/composable-tables/vite.config.ts b/examples/vue/composable-tables/vite.config.ts new file mode 100644 index 0000000000..3dec1c870d --- /dev/null +++ b/examples/vue/composable-tables/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' + +export default defineConfig({ + plugins: [vue(), vueJsx()], +}) diff --git a/examples/vue/expanding/README.md b/examples/vue/expanding/README.md new file mode 100644 index 0000000000..3ac3f1a9b4 --- /dev/null +++ b/examples/vue/expanding/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run dev` or `yarn dev` diff --git a/examples/vue/expanding/env.d.ts b/examples/vue/expanding/env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/vue/expanding/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/vue/expanding/index.html b/examples/vue/expanding/index.html new file mode 100644 index 0000000000..dcd696757f --- /dev/null +++ b/examples/vue/expanding/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/examples/vue/expanding/package.json b/examples/vue/expanding/package.json new file mode 100644 index 0000000000..2ef4821702 --- /dev/null +++ b/examples/vue/expanding/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-vue-table-example-expanding", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint ./src", + "test:types": "vue-tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/vue-table": "^9.0.0-alpha.45", + "vue": "^3.5.34" + }, + "devDependencies": { + "@types/node": "^25.6.2", + "@vitejs/plugin-vue": "^6.0.6", + "@vitejs/plugin-vue-jsx": "^5.1.5", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vue-tsc": "^3.2.8" + } +} diff --git a/examples/vue/expanding/src/App.tsx b/examples/vue/expanding/src/App.tsx new file mode 100644 index 0000000000..4bad74a9ba --- /dev/null +++ b/examples/vue/expanding/src/App.tsx @@ -0,0 +1,335 @@ +import { defineComponent, ref, watchEffect } from 'vue' +import { + FlexRender, + columnFilteringFeature, + createColumnHelper, + createExpandedRowModel, + createFilteredRowModel, + createPaginatedRowModel, + createSortedRowModel, + filterFns, + rowExpandingFeature, + rowPaginationFeature, + rowSelectionFeature, + rowSortingFeature, + sortFns, + tableFeatures, + useTable, +} from '@tanstack/vue-table' +import { makeData } from './makeData' +import type { + Cell, + Column, + Header, + HeaderGroup, + Row, + Table, +} from '@tanstack/vue-table' +import type { Person } from './makeData' + +const _features = tableFeatures({ + columnFilteringFeature, + rowExpandingFeature, + rowPaginationFeature, + rowSortingFeature, + rowSelectionFeature, +}) + +const columnHelper = createColumnHelper() + +const IndeterminateCheckbox = defineComponent({ + name: 'IndeterminateCheckbox', + props: { + checked: Boolean, + indeterminate: Boolean, + onChange: Function, + className: String, + }, + setup(props) { + const inputRef = ref(null) + + watchEffect(() => { + if (inputRef.value) { + inputRef.value.indeterminate = !props.checked && !!props.indeterminate + } + }) + + return () => ( + + ) + }, +}) + +function renderFilter( + column: Column, + table: Table, +) { + const firstValue = table + .getPreFilteredRowModel() + .flatRows[0]?.getValue(column.id) + + const columnFilterValue = column.getFilterValue() + + if (typeof firstValue === 'number') { + return ( +
+ + column.setFilterValue((old: [number, number] | undefined) => { + const target = event.currentTarget as HTMLInputElement + return [target.value, old?.[1]] + }) + } + placeholder="Min" + class="filter-input" + /> + + column.setFilterValue((old: [number, number] | undefined) => { + const target = event.currentTarget as HTMLInputElement + return [old?.[0], target.value] + }) + } + placeholder="Max" + class="filter-input" + /> +
+ ) + } + + return ( + + column.setFilterValue((event.currentTarget as HTMLInputElement).value) + } + placeholder="Search..." + class="filter-select" + /> + ) +} + +export default defineComponent({ + name: 'ExpandingExample', + setup() { + const data = ref(makeData(100, 5, 3)) + const refreshData = () => { + data.value = makeData(100, 5, 3) + } + const stressTest = () => { + data.value = makeData(10_000, 5, 3) + } + + const columns = columnHelper.columns([ + columnHelper.accessor('firstName', { + header: ({ table }) => ( + <> + {' '} + {' '} + First Name + + ), + cell: ({ row, getValue }) => ( +
+
+ {' '} + {row.getCanExpand() ? ( + + ) : ( + '🔵' + )}{' '} + {getValue()} +
+
+ ), + footer: (props) => props.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + footer: (props) => props.column.id, + }), + columnHelper.accessor('age', { + header: () => 'Age', + footer: (props) => props.column.id, + filterFn: 'between', + }), + columnHelper.accessor('visits', { + header: () => Visits, + footer: (props) => props.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + footer: (props) => props.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + footer: (props) => props.column.id, + }), + ]) + + const table = useTable({ + _features, + _rowModels: { + expandedRowModel: createExpandedRowModel(), + filteredRowModel: createFilteredRowModel(filterFns), + paginatedRowModel: createPaginatedRowModel(), + sortedRowModel: createSortedRowModel(sortFns), + }, + columns, + get data() { + return data.value + }, + getSubRows: (row: Person) => row.subRows, + debugTable: true, + }) + + return () => ( +
+
+ + +
+
+ + + {table + .getHeaderGroups() + .map((headerGroup: HeaderGroup) => ( + + {headerGroup.headers.map( + (header: Header) => ( + + ), + )} + + ))} + + + {table + .getRowModel() + .rows.map((row: Row) => ( + + {row + .getAllCells() + .map((cell: Cell) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( +
+ + {header.column.getCanFilter() ? ( +
{renderFilter(header.column, table)}
+ ) : null} +
+ )} +
+ +
+
+
+ + + + + +
Page
+ + {(table.store.state.pagination.pageIndex + 1).toLocaleString()} of{' '} + {table.getPageCount().toLocaleString()} + +
+ + | Go to page: + { + const target = event.currentTarget as HTMLInputElement + const page = target.value ? Number(target.value) - 1 : 0 + table.setPageIndex(page) + }} + class="page-size-input" + /> + + +
+
{table.getRowModel().rows.length.toLocaleString()} Rows
+
{JSON.stringify(table.store.state, null, 2)}
+
+ ) + }, +}) diff --git a/examples/vue/expanding/src/App.vue b/examples/vue/expanding/src/App.vue new file mode 100644 index 0000000000..4256c2000a --- /dev/null +++ b/examples/vue/expanding/src/App.vue @@ -0,0 +1,7 @@ + + + diff --git a/examples/vue/expanding/src/env.d.ts b/examples/vue/expanding/src/env.d.ts new file mode 100644 index 0000000000..6977c557dc --- /dev/null +++ b/examples/vue/expanding/src/env.d.ts @@ -0,0 +1,8 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/examples/vue/expanding/src/index.css b/examples/vue/expanding/src/index.css new file mode 100644 index 0000000000..40a5eb73a4 --- /dev/null +++ b/examples/vue/expanding/src/index.css @@ -0,0 +1,360 @@ +html { + font-family: sans-serif; + font-size: 14px; +} +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} +tbody { + border-bottom: 1px solid lightgray; +} +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} +tfoot { + color: gray; +} +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/vue/expanding/src/main.ts b/examples/vue/expanding/src/main.ts new file mode 100644 index 0000000000..50a4dab0d5 --- /dev/null +++ b/examples/vue/expanding/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import App from './App.vue' +import './index.css' + +createApp(App).mount('#app') diff --git a/examples/vue/expanding/src/makeData.ts b/examples/vue/expanding/src/makeData.ts new file mode 100644 index 0000000000..d63c724b74 --- /dev/null +++ b/examples/vue/expanding/src/makeData.ts @@ -0,0 +1,43 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (): Person => ({ + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (): Person => ({ + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/vue/expanding/tsconfig.json b/examples/vue/expanding/tsconfig.json new file mode 100644 index 0000000000..500d5a5148 --- /dev/null +++ b/examples/vue/expanding/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/vue/expanding/vite.config.ts b/examples/vue/expanding/vite.config.ts new file mode 100644 index 0000000000..3dec1c870d --- /dev/null +++ b/examples/vue/expanding/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' + +export default defineConfig({ + plugins: [vue(), vueJsx()], +}) diff --git a/examples/vue/filters-faceted/README.md b/examples/vue/filters-faceted/README.md new file mode 100644 index 0000000000..3ac3f1a9b4 --- /dev/null +++ b/examples/vue/filters-faceted/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run dev` or `yarn dev` diff --git a/examples/vue/filters-faceted/env.d.ts b/examples/vue/filters-faceted/env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/vue/filters-faceted/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/vue/filters-faceted/index.html b/examples/vue/filters-faceted/index.html new file mode 100644 index 0000000000..dcd696757f --- /dev/null +++ b/examples/vue/filters-faceted/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/examples/vue/filters-faceted/package.json b/examples/vue/filters-faceted/package.json new file mode 100644 index 0000000000..7784624837 --- /dev/null +++ b/examples/vue/filters-faceted/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-vue-table-example-filters-faceted", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint ./src", + "test:types": "vue-tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/vue-table": "^9.0.0-alpha.45", + "vue": "^3.5.34" + }, + "devDependencies": { + "@types/node": "^25.6.2", + "@vitejs/plugin-vue": "^6.0.6", + "@vitejs/plugin-vue-jsx": "^5.1.5", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vue-tsc": "^3.2.8" + } +} diff --git a/examples/vue/filters-faceted/src/App.vue b/examples/vue/filters-faceted/src/App.vue new file mode 100644 index 0000000000..3bc13726eb --- /dev/null +++ b/examples/vue/filters-faceted/src/App.vue @@ -0,0 +1,249 @@ + + + + + diff --git a/examples/vue/filters-faceted/src/env.d.ts b/examples/vue/filters-faceted/src/env.d.ts new file mode 100644 index 0000000000..6977c557dc --- /dev/null +++ b/examples/vue/filters-faceted/src/env.d.ts @@ -0,0 +1,8 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/examples/vue/filters-faceted/src/index.css b/examples/vue/filters-faceted/src/index.css new file mode 100644 index 0000000000..b9af6ded32 --- /dev/null +++ b/examples/vue/filters-faceted/src/index.css @@ -0,0 +1,365 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} + +tbody { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} + +tfoot { + color: gray; +} + +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/vue/filters-faceted/src/main.ts b/examples/vue/filters-faceted/src/main.ts new file mode 100644 index 0000000000..50a4dab0d5 --- /dev/null +++ b/examples/vue/filters-faceted/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import App from './App.vue' +import './index.css' + +createApp(App).mount('#app') diff --git a/examples/vue/filters-faceted/src/makeData.ts b/examples/vue/filters-faceted/src/makeData.ts new file mode 100644 index 0000000000..b9055b2d8c --- /dev/null +++ b/examples/vue/filters-faceted/src/makeData.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], + } +} + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + } + }) + } + + return makeDataLevel() +} diff --git a/examples/vue/filters-faceted/tsconfig.json b/examples/vue/filters-faceted/tsconfig.json new file mode 100644 index 0000000000..500d5a5148 --- /dev/null +++ b/examples/vue/filters-faceted/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/vue/filters-faceted/vite.config.ts b/examples/vue/filters-faceted/vite.config.ts new file mode 100644 index 0000000000..3dec1c870d --- /dev/null +++ b/examples/vue/filters-faceted/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' + +export default defineConfig({ + plugins: [vue(), vueJsx()], +}) diff --git a/examples/vue/filters-fuzzy/README.md b/examples/vue/filters-fuzzy/README.md new file mode 100644 index 0000000000..3ac3f1a9b4 --- /dev/null +++ b/examples/vue/filters-fuzzy/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run dev` or `yarn dev` diff --git a/examples/vue/filters-fuzzy/env.d.ts b/examples/vue/filters-fuzzy/env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/vue/filters-fuzzy/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/vue/filters-fuzzy/index.html b/examples/vue/filters-fuzzy/index.html new file mode 100644 index 0000000000..dcd696757f --- /dev/null +++ b/examples/vue/filters-fuzzy/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/examples/vue/filters-fuzzy/package.json b/examples/vue/filters-fuzzy/package.json new file mode 100644 index 0000000000..457e3ca9c3 --- /dev/null +++ b/examples/vue/filters-fuzzy/package.json @@ -0,0 +1,24 @@ +{ + "name": "tanstack-vue-table-example-filters-fuzzy", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint ./src", + "test:types": "vue-tsc" + }, + "dependencies": { + "@faker-js/faker": "^10.4.0", + "@tanstack/vue-table": "^9.0.0-alpha.45", + "vue": "^3.5.34" + }, + "devDependencies": { + "@types/node": "^25.6.2", + "@vitejs/plugin-vue": "^6.0.6", + "@vitejs/plugin-vue-jsx": "^5.1.5", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vue-tsc": "^3.2.8" + } +} diff --git a/examples/vue/filters-fuzzy/src/App.vue b/examples/vue/filters-fuzzy/src/App.vue new file mode 100644 index 0000000000..3bc13726eb --- /dev/null +++ b/examples/vue/filters-fuzzy/src/App.vue @@ -0,0 +1,249 @@ + + + + + diff --git a/examples/vue/filters-fuzzy/src/env.d.ts b/examples/vue/filters-fuzzy/src/env.d.ts new file mode 100644 index 0000000000..6977c557dc --- /dev/null +++ b/examples/vue/filters-fuzzy/src/env.d.ts @@ -0,0 +1,8 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/examples/vue/filters-fuzzy/src/index.css b/examples/vue/filters-fuzzy/src/index.css new file mode 100644 index 0000000000..40a5eb73a4 --- /dev/null +++ b/examples/vue/filters-fuzzy/src/index.css @@ -0,0 +1,360 @@ +html { + font-family: sans-serif; + font-size: 14px; +} +table { + border: 1px solid lightgray; + border-collapse: collapse; + border-spacing: 0; +} +tbody { + border-bottom: 1px solid lightgray; +} +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; +} +tfoot { + color: gray; +} +tfoot th { + font-weight: normal; +} + +/* Demo layout utilities for plain example styling. */ +.demo-root { + padding: 0.5rem; +} + +.spacer-xs { + height: 0.25rem; +} + +.spacer-sm { + height: 0.5rem; +} + +.spacer-md { + height: 1rem; +} + +.controls, +.button-row, +.inline-controls, +.pin-actions, +.filter-row, +.form-actions { + display: flex; + align-items: center; +} + +.button-row { + flex-wrap: wrap; + gap: 0.5rem; +} + +.controls { + gap: 0.5rem; +} + +.inline-controls, +.pin-actions { + gap: 0.25rem; +} + +.pin-actions { + justify-content: center; +} + +.filter-row { + gap: 0.5rem; +} + +.form-actions { + gap: 1rem; + margin-bottom: 1rem; +} + +.split-tables { + display: flex; + gap: 1rem; +} + +.table-row-group { + display: flex; + border-collapse: collapse; + border-spacing: 0; +} + +.vertical-options { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; +} + +.column-toggle-panel { + display: inline-block; + border: 1px solid #000; + border-radius: 0.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.column-toggle-panel-header { + border-bottom: 1px solid #000; + padding: 0 0.25rem; +} + +.column-toggle-row, +.selection-cell { + padding: 0 0.25rem; +} + +.selection-cell { + display: block; +} + +.demo-button, +.pin-button, +.compact-input, +.filter-input, +.filter-select, +.page-size-input, +.text-input, +.number-input, +.wide-action-button, +.primary-action, +.secondary-action, +.success-action { + border: 1px solid currentColor; + border-radius: 0.25rem; +} + +.demo-button { + padding: 0.5rem; +} + +.demo-button-sm { + padding: 0.25rem; +} + +.demo-button-spaced { + margin-bottom: 0.5rem; +} + +.pin-button { + padding: 0 0.5rem; +} + +.outlined-table { + border: 2px solid #000; + border-collapse: collapse; + border-spacing: 0; +} + +.outlined-control { + border-color: #000; +} + +.nowrap { + white-space: nowrap; +} + +.demo-note { + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.section-title { + font-size: 1.25rem; +} + +.scroll-container { + overflow-x: auto; +} + +.page-size-input { + width: 4rem; + padding: 0.25rem; +} + +.number-input { + width: 5rem; + padding: 0 0.25rem; +} + +.filter-input, +.filter-select { + width: 6rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); +} + +.filter-select { + width: 9rem; +} + +.text-input { + width: 100%; + padding: 0 0.25rem; +} + +.compact-input { + padding: 0 0.25rem; +} + +.wide-action-button { + width: 16rem; +} + +.summary-panel { + border: 1px solid currentColor; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.2); + padding: 0.5rem; +} + +.sortable-header, +.sortable { + cursor: pointer; + user-select: none; +} + +.primary-action, +.success-action, +.secondary-action { + color: #fff; +} + +.primary-action { + background: #3b82f6; +} + +.success-action { + background: #22c55e; +} + +.secondary-action { + background: #6b7280; +} + +.submit-button:disabled { + opacity: 0.5; +} + +.error-text { + color: #ef4444; + font-size: 0.75rem; +} + +.success-text { + color: #16a34a; +} + +.warning-text { + color: #ca8a04; +} + +.muted-text { + color: #9ca3af; +} + +.label-offset { + margin-left: 0.5rem; +} + +.cell-padding { + padding: 0.25rem; +} + +.table-spacer { + margin-bottom: 0.5rem; + border-collapse: collapse; + border-spacing: 0; +} + +.warning-panel { + margin-bottom: 1rem; + border: 1px solid #facc15; + border-radius: 0.25rem; + background: #fef9c3; + padding: 0.5rem; +} + +.code-block { + overflow: auto; + border-radius: 0.25rem; + background: #f3f4f6; + padding: 0.5rem; + font-size: 0.75rem; +} + +.router-root { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; +} + +.page-title { + margin-bottom: 0.25rem; + font-size: 1.5rem; + font-weight: 600; +} + +.disabled-button:disabled { + color: #6b7280; + cursor: not-allowed; +} + +.pagination-controls { + margin-block: 0.5rem; +} + +.column-size-input { + width: 6rem; + margin-left: 0.5rem; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.25rem; +} + +.form-status { + display: flex; + gap: 1rem; + font-size: 0.875rem; +} + +.centered-button-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; +} + +.split-gap { + gap: 1rem; +} + +.demo-link { + color: #2563eb; + text-decoration: underline; +} + +.capitalized-text { + text-transform: capitalize; +} + +.centered-text { + text-align: center; +} + +.centered-strong-text { + text-align: center; + font-weight: 600; +} + +.virtualized-title { + text-align: center; + font-size: 1.875rem; + font-weight: 700; +} diff --git a/examples/vue/filters-fuzzy/src/main.ts b/examples/vue/filters-fuzzy/src/main.ts new file mode 100644 index 0000000000..50a4dab0d5 --- /dev/null +++ b/examples/vue/filters-fuzzy/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import App from './App.vue' +import './index.css' + +createApp(App).mount('#app') diff --git a/examples/vue/filters-fuzzy/src/makeData.ts b/examples/vue/filters-fuzzy/src/makeData.ts new file mode 100644 index 0000000000..38c1db1f15 --- /dev/null +++ b/examples/vue/filters-fuzzy/src/makeData.ts @@ -0,0 +1,45 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + id: number + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + subRows?: Array +} + +const range = (len: number) => { + const arr: Array = [] + for (let i = 0; i < len; i++) arr.push(i) + return arr +} + +const newPerson = (num: number): Person => ({ + id: num, + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0], +}) + +export function makeData(...lens: Array) { + const makeDataLevel = (depth = 0): Array => { + const len = lens[depth] + return range(len).map( + (index): Person => ({ + ...newPerson(index), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }), + ) + } + return makeDataLevel() +} diff --git a/examples/vue/filters-fuzzy/tsconfig.json b/examples/vue/filters-fuzzy/tsconfig.json new file mode 100644 index 0000000000..500d5a5148 --- /dev/null +++ b/examples/vue/filters-fuzzy/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue", + "vite.config.js", + "vite.config.ts" + ] +} diff --git a/examples/vue/filters-fuzzy/vite.config.ts b/examples/vue/filters-fuzzy/vite.config.ts new file mode 100644 index 0000000000..3dec1c870d --- /dev/null +++ b/examples/vue/filters-fuzzy/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' + +export default defineConfig({ + plugins: [vue(), vueJsx()], +}) diff --git a/examples/vue/filters/index.html b/examples/vue/filters/index.html index e160e46247..dcd696757f 100644 --- a/examples/vue/filters/index.html +++ b/examples/vue/filters/index.html @@ -5,7 +5,6 @@ Vite App -
diff --git a/examples/vue/filters/package.json b/examples/vue/filters/package.json index e22f06b805..04242a5981 100644 --- a/examples/vue/filters/package.json +++ b/examples/vue/filters/package.json @@ -1,23 +1,24 @@ { - "name": "tanstack-table-example-vue-filters", + "name": "tanstack-vue-table-example-filters", "private": true, - "version": "0.0.0", "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview", + "lint": "eslint ./src", "test:types": "vue-tsc" }, "dependencies": { - "@faker-js/faker": "^8.4.1", - "vue": "^3.4.31", - "@tanstack/vue-table": "^8.20.5" + "@faker-js/faker": "^10.4.0", + "@tanstack/vue-table": "^9.0.0-alpha.45", + "vue": "^3.5.34" }, "devDependencies": { - "@types/node": "^20.14.9", - "@vitejs/plugin-vue": "^5.0.5", - "typescript": "5.4.5", - "vite": "^5.3.2", - "vue-tsc": "^2.0.22" + "@types/node": "^25.6.2", + "@vitejs/plugin-vue": "^6.0.6", + "@vitejs/plugin-vue-jsx": "^5.1.5", + "typescript": "6.0.3", + "vite": "^8.0.11", + "vue-tsc": "^3.2.8" } } diff --git a/examples/vue/filters/src/App.vue b/examples/vue/filters/src/App.vue index 967c384957..c23b51b498 100644 --- a/examples/vue/filters/src/App.vue +++ b/examples/vue/filters/src/App.vue @@ -1,148 +1,119 @@