diff --git a/.gitignore b/.gitignore
index 1ef516313f..14ada71ef6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,6 +35,7 @@ stats-hydration.json
stats-react.json
stats.html
.vscode/settings.json
+.idea
*.log
.DS_Store
diff --git a/examples/lit/basic/.gitignore b/examples/lit/basic/.gitignore
new file mode 100644
index 0000000000..d451ff16c1
--- /dev/null
+++ b/examples/lit/basic/.gitignore
@@ -0,0 +1,5 @@
+node_modules
+.DS_Store
+dist
+dist-ssr
+*.local
diff --git a/examples/lit/basic/README.md b/examples/lit/basic/README.md
new file mode 100644
index 0000000000..b168d3c4b1
--- /dev/null
+++ b/examples/lit/basic/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/lit/basic/index.html b/examples/lit/basic/index.html
new file mode 100644
index 0000000000..9807874fae
--- /dev/null
+++ b/examples/lit/basic/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+ Vite App
+
+
+
+
+
+
+
+
diff --git a/examples/lit/basic/package.json b/examples/lit/basic/package.json
new file mode 100644
index 0000000000..482df5ea48
--- /dev/null
+++ b/examples/lit/basic/package.json
@@ -0,0 +1,20 @@
+{
+ "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": "workspace:*",
+ "lit": "^3.1.3"
+ },
+ "devDependencies": {
+ "@rollup/plugin-replace": "^5.0.5",
+ "typescript": "5.4.5",
+ "vite": "^5.2.10"
+ }
+}
diff --git a/examples/lit/basic/src/index.css b/examples/lit/basic/src/index.css
new file mode 100644
index 0000000000..43c09e0f6b
--- /dev/null
+++ b/examples/lit/basic/src/index.css
@@ -0,0 +1,26 @@
+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/lit/basic/src/main.ts b/examples/lit/basic/src/main.ts
new file mode 100644
index 0000000000..d462cc01b5
--- /dev/null
+++ b/examples/lit/basic/src/main.ts
@@ -0,0 +1,225 @@
+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'
+
+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 => 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')
+class LitTableExample extends LitElement {
+ private tableController = new TableController(this)
+
+ protected render(): unknown {
+ const table = this.tableController.getTable({
+ columns,
+ data,
+ getCoreRowModel: getCoreRowModel(),
+ })
+
+ return html`
+
+
+ ${repeat(
+ table.getHeaderGroups(),
+ headerGroup => headerGroup.id,
+ headerGroup =>
+ html`${repeat(
+ headerGroup.headers,
+ header => header.id,
+ header =>
+ html`
+ ${header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext()
+ )}
+ | `
+ )}`
+ )}
+
+
+ ${repeat(
+ table.getRowModel().rows,
+ row => row.id,
+ row => html`
+
+ ${repeat(
+ row.getVisibleCells(),
+ cell => cell.id,
+ cell =>
+ html`
+ ${flexRender(
+ cell.column.columnDef.cell,
+ cell.getContext()
+ )}
+ | `
+ )}
+
+ `
+ )}
+
+
+ ${repeat(
+ table.getFooterGroups(),
+ footerGroup => footerGroup.id,
+ footerGroup => html`
+
+ ${repeat(
+ footerGroup.headers,
+ header => header.id,
+ header => html`
+
+ ${header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.footer,
+ header.getContext()
+ )}
+ |
+ `
+ )}
+
+ `
+ )}
+
+
+
+ `
+ }
+}
diff --git a/examples/lit/basic/tsconfig.json b/examples/lit/basic/tsconfig.json
new file mode 100644
index 0000000000..3141563c8a
--- /dev/null
+++ b/examples/lit/basic/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "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/vite.config.js b/examples/lit/basic/vite.config.js
new file mode 100644
index 0000000000..ab1e1380f8
--- /dev/null
+++ b/examples/lit/basic/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/packages/lit-table/package.json b/packages/lit-table/package.json
new file mode 100644
index 0000000000..fde205cfab
--- /dev/null
+++ b/packages/lit-table/package.json
@@ -0,0 +1,62 @@
+{
+ "name": "@tanstack/lit-table",
+ "version": "0.0.0",
+ "description": "Headless UI for building powerful tables & datagrids for Lit.",
+ "author": "Tanner Linsley",
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/TanStack/table.git",
+ "directory": "packages/lit-table"
+ },
+ "homepage": "https://tanstack.com/table",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "keywords": [
+ "lit",
+ "table",
+ "lit-table",
+ "datagrid"
+ ],
+ "type": "commonjs",
+ "module": "build/lib/index.esm.js",
+ "main": "build/lib/index.js",
+ "types": "build/lib/index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./build/lib/index.d.ts",
+ "import": "./build/lib/index.mjs",
+ "default": "./build/lib/index.js"
+ },
+ "./package.json": "./package.json"
+ },
+ "sideEffects": false,
+ "engines": {
+ "node": ">=12"
+ },
+ "files": [
+ "build/lib/*",
+ "build/umd/*",
+ "src"
+ ],
+ "scripts": {
+ "clean": "rimraf ./build",
+ "test:lib": "vitest",
+ "test:lib:dev": "pnpm test:lib --watch",
+ "test:types": "tsc --noEmit",
+ "build": "pnpm build:rollup && pnpm build:types",
+ "build:rollup": "rollup --config rollup.config.mjs",
+ "build:types": "tsc --emitDeclarationOnly"
+ },
+ "dependencies": {
+ "@tanstack/table-core": "workspace:*"
+ },
+ "devDependencies": {
+ "lit": "^3.1.3"
+ },
+ "peerDependencies": {
+ "lit": "^3.1.3"
+ }
+}
diff --git a/packages/lit-table/rollup.config.mjs b/packages/lit-table/rollup.config.mjs
new file mode 100644
index 0000000000..0e4b3b9a92
--- /dev/null
+++ b/packages/lit-table/rollup.config.mjs
@@ -0,0 +1,17 @@
+// @ts-check
+
+import { defineConfig } from 'rollup'
+import { buildConfigs } from '../../scripts/getRollupConfig.js'
+
+export default defineConfig(
+ buildConfigs({
+ name: 'lit-table',
+ jsName: 'LitTable',
+ outputFile: 'index',
+ entryFile: 'src/index.ts',
+ external: ['lit'],
+ globals: {
+ lit: 'lit',
+ },
+ })
+)
diff --git a/packages/lit-table/src/index.ts b/packages/lit-table/src/index.ts
new file mode 100755
index 0000000000..5cb3b5cdbe
--- /dev/null
+++ b/packages/lit-table/src/index.ts
@@ -0,0 +1,67 @@
+import { ReactiveController, ReactiveControllerHost } from 'lit'
+import {
+ RowData,
+ Table,
+ TableOptions,
+ TableOptionsResolved,
+ TableState,
+ createTable,
+} from '@tanstack/table-core'
+
+export * from '@tanstack/table-core'
+
+export function flexRender(
+ Comp: ((props: TProps) => string) | string | undefined,
+ props: TProps
+): string | null {
+ if (!Comp) return null
+
+ if (typeof Comp === 'function') {
+ return Comp(props)
+ }
+
+ return Comp
+}
+
+export class TableController
+ implements ReactiveController
+{
+ host: ReactiveControllerHost
+
+ private table: Table | null = null
+
+ private _tableState: TableState | null = null
+
+ constructor(host: ReactiveControllerHost) {
+ ;(this.host = host).addController(this)
+ }
+
+ public getTable(options: TableOptions) {
+ const resolvedOptions: TableOptionsResolved = {
+ state: {},
+ onStateChange: () => {}, // noop
+ renderFallbackValue: null,
+ ...options,
+ }
+
+ if (!this.table) {
+ this.table = createTable(resolvedOptions)
+ this._tableState = this.table.initialState
+ }
+
+ this.table.setOptions({
+ ...resolvedOptions,
+ state: { ...this._tableState, ...(options.state || {}) },
+ onStateChange: (updater: any) => {
+ this._tableState = updater
+ this.host.requestUpdate()
+
+ options.onStateChange?.(updater)
+ },
+ })
+
+ return this.table
+ }
+
+ hostConnected() {}
+}
diff --git a/packages/lit-table/tsconfig.json b/packages/lit-table/tsconfig.json
new file mode 100644
index 0000000000..062f8964b3
--- /dev/null
+++ b/packages/lit-table/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "react",
+ "rootDir": "./src",
+ "outDir": "./build/lib",
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true
+ },
+ "include": ["src"]
+}
diff --git a/packages/lit-table/vitest.config.ts b/packages/lit-table/vitest.config.ts
new file mode 100644
index 0000000000..20690b6ad0
--- /dev/null
+++ b/packages/lit-table/vitest.config.ts
@@ -0,0 +1,10 @@
+import { defineConfig } from 'vitest/config'
+
+export default defineConfig({
+ test: {
+ watch: false,
+ environment: 'jsdom',
+ globals: true,
+ dir: '__tests__',
+ },
+})
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ac0cf2611c..a206c25b3c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -99,6 +99,25 @@ importers:
specifier: ^1.5.2
version: 1.5.2(@types/node@20.12.7)(jsdom@24.0.0)
+ examples/lit/basic:
+ dependencies:
+ '@tanstack/lit-table':
+ specifier: workspace:*
+ version: link:../../../packages/lit-table
+ lit:
+ specifier: ^3.1.3
+ version: 3.1.3
+ devDependencies:
+ '@rollup/plugin-replace':
+ specifier: ^5.0.5
+ version: 5.0.5(rollup@4.16.4)
+ typescript:
+ specifier: 5.4.5
+ version: 5.4.5
+ vite:
+ specifier: ^5.2.10
+ version: 5.2.10(@types/node@20.12.7)
+
examples/qwik/basic:
dependencies:
'@tanstack/qwik-table':
@@ -1904,6 +1923,16 @@ importers:
specifier: ^2.0.14
version: 2.0.14(typescript@5.4.5)
+ packages/lit-table:
+ dependencies:
+ '@tanstack/table-core':
+ specifier: workspace:*
+ version: link:../table-core
+ devDependencies:
+ lit:
+ specifier: ^3.1.3
+ version: 3.1.3
+
packages/match-sorter-utils:
dependencies:
remove-accents:
@@ -4004,6 +4033,14 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.15
dev: true
+ /@lit-labs/ssr-dom-shim@1.2.0:
+ resolution: {integrity: sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g==}
+
+ /@lit/reactive-element@2.0.4:
+ resolution: {integrity: sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==}
+ dependencies:
+ '@lit-labs/ssr-dom-shim': 1.2.0
+
/@microsoft/api-extractor-model@7.28.13(@types/node@20.12.7):
resolution: {integrity: sha512-39v/JyldX4MS9uzHcdfmjjfS6cYGAoXV+io8B5a338pkHiSt+gy2eXQ0Q7cGFJ7quSa1VqqlMdlPrB6sLR/cAw==}
dependencies:
@@ -4510,7 +4547,7 @@ packages:
optional: true
dependencies:
'@rollup/pluginutils': 5.1.0(rollup@4.16.4)
- magic-string: 0.30.8
+ magic-string: 0.30.10
rollup: 4.16.4
dev: true
@@ -5071,6 +5108,9 @@ packages:
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
dev: true
+ /@types/trusted-types@2.0.7:
+ resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
+
/@types/warning@3.0.3:
resolution: {integrity: sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==}
dev: false
@@ -5137,7 +5177,7 @@ packages:
/@vitest/snapshot@1.5.2:
resolution: {integrity: sha512-CTEp/lTYos8fuCc9+Z55Ga5NVPKUgExritjF5VY7heRFUfheoAqBneUlvXSUJHUZPjnPmyZA96yLRJDP1QATFQ==}
dependencies:
- magic-string: 0.30.8
+ magic-string: 0.30.10
pathe: 1.1.2
pretty-format: 29.7.0
dev: true
@@ -5277,7 +5317,7 @@ packages:
'@vue/compiler-ssr': 3.4.21
'@vue/shared': 3.4.21
estree-walker: 2.0.2
- magic-string: 0.30.8
+ magic-string: 0.30.10
postcss: 8.4.38
source-map-js: 1.2.0
dev: true
@@ -7402,6 +7442,25 @@ packages:
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dev: true
+ /lit-element@4.0.5:
+ resolution: {integrity: sha512-iTWskWZEtn9SyEf4aBG6rKT8GABZMrTWop1+jopsEOgEcugcXJGKuX5bEbkq9qfzY+XB4MAgCaSPwnNpdsNQ3Q==}
+ dependencies:
+ '@lit-labs/ssr-dom-shim': 1.2.0
+ '@lit/reactive-element': 2.0.4
+ lit-html: 3.1.3
+
+ /lit-html@3.1.3:
+ resolution: {integrity: sha512-FwIbqDD8O/8lM4vUZ4KvQZjPPNx7V1VhT7vmRB8RBAO0AU6wuTVdoXiu2CivVjEGdugvcbPNBLtPE1y0ifplHA==}
+ dependencies:
+ '@types/trusted-types': 2.0.7
+
+ /lit@3.1.3:
+ resolution: {integrity: sha512-l4slfspEsnCcHVRTvaP7YnkTZEZggNFywLEIhQaGhYDczG+tu/vlgm/KaWIEjIp+ZyV20r2JnZctMb8LeLCG7Q==}
+ dependencies:
+ '@lit/reactive-element': 2.0.4
+ lit-element: 4.0.5
+ lit-html: 3.1.3
+
/local-pkg@0.5.0:
resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==}
engines: {node: '>=14'}
@@ -9147,7 +9206,7 @@ packages:
'@babel/core': 7.24.4
'@types/pug': 2.0.10
detect-indent: 6.1.0
- magic-string: 0.30.8
+ magic-string: 0.30.10
sorcery: 0.11.0
strip-indent: 3.0.0
svelte: 4.2.15
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 201a1c573f..d4836d8d08 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -1,6 +1,7 @@
packages:
- 'packages/*'
- 'examples/qwik/*'
+ - 'examples/lit/*'
- 'examples/react/*'
- 'examples/solid/*'
- 'examples/svelte/*'