From 7afe94132042bdf04079af28ed4796794cd1bf32 Mon Sep 17 00:00:00 2001 From: Frederic Bahr Date: Sat, 16 May 2026 19:33:32 +0200 Subject: [PATCH] feat: use lit-store internally instead of reyling on vanilla store dependency --- packages/lit-table/package.json | 4 +- packages/lit-table/src/TableController.ts | 83 +++++++++++++++++++++-- packages/lit-table/src/reactivity.ts | 2 +- pnpm-lock.yaml | 53 +++++++-------- 4 files changed, 102 insertions(+), 40 deletions(-) diff --git a/packages/lit-table/package.json b/packages/lit-table/package.json index e1cd8b03ae..c43a9a8150 100644 --- a/packages/lit-table/package.json +++ b/packages/lit-table/package.json @@ -57,8 +57,8 @@ "build": "tsdown" }, "dependencies": { - "@tanstack/store": "^0.11.0", - "@tanstack/table-core": "^9.0.0-beta.0" + "@tanstack/lit-store": "^0.13.2", + "@tanstack/table-core": "workspace:*" }, "devDependencies": { "@lit/context": "^1.1.6", diff --git a/packages/lit-table/src/TableController.ts b/packages/lit-table/src/TableController.ts index 447b30be9f..2e5973d5ff 100644 --- a/packages/lit-table/src/TableController.ts +++ b/packages/lit-table/src/TableController.ts @@ -1,7 +1,13 @@ import { constructTable } from '@tanstack/table-core' +import { TanStackStoreSelector } from '@tanstack/lit-store' import { litReactivity } from './reactivity' import { FlexRender } from './flexRender' -import type { Atom, ReadonlyAtom, ReadonlyStore, Store } from '@tanstack/store' +import type { + Atom, + ReadonlyAtom, + ReadonlyStore, + Store, +} from '@tanstack/lit-store' import type { NoInfer, RowData, @@ -146,6 +152,13 @@ export class TableController< private _storeSubscription?: { unsubscribe: () => void } private _optionsSubscription?: { unsubscribe: () => void } private _notifier = 0 + private _selectorCache = new WeakMap< + SubscribeSource, + Map< + ((state: unknown) => unknown) | undefined, + TanStackStoreSelector + > + >() constructor(host: ReactiveControllerHost) { ;(this.host = host).addController(this) @@ -205,23 +218,28 @@ export class TableController< const tableInstance = this._table // Attach Subscribe function - const Subscribe = function Subscribe(props: { + const Subscribe = ((props: { source?: SubscribeSource selector?: (state: unknown) => unknown children: | ((state: Readonly) => TemplateResult | string) | TemplateResult | string - }): TemplateResult | string { + }): TemplateResult | string => { const source = props.source ?? tableInstance.store - const value = source.get() - const selectedState = - props.selector !== undefined ? props.selector(value) : value + + const storeSelector: TanStackStoreSelector = + this._getOrCreateSelector(source, props.selector) + + // TODO: update to newest version of Tanstack Store: https://github.com/TanStack/store/pull/329 + const selectedState = storeSelector.value + if (typeof props.children === 'function') { return props.children(selectedState as Readonly) } + return props.children - } as LitTable['Subscribe'] + }) as LitTable['Subscribe'] return { ...this._table, @@ -257,5 +275,56 @@ export class TableController< this._storeSubscription = undefined this._optionsSubscription?.unsubscribe() this._optionsSubscription = undefined + this._selectorCache = new WeakMap() + } + + /** + * Get or create a TanStackStoreSelector for the given source and selector. + * + * Caches selectors by source (WeakMap) and selector function to avoid + * creating new controllers on every render cycle. + * + * @param source The atom or store to subscribe to + * @param selector Optional selector function to select a slice of the source state + * @returns A cached TanStackStoreSelector instance that subscribes to the source and applies the selector + */ + private _getOrCreateSelector = ( + source?: SubscribeSource, + selector?: (state: unknown) => unknown, + ): TanStackStoreSelector => { + if (!source) { + return new TanStackStoreSelector(this.host, () => source, selector) + } + + if (!this._selectorCache.has(source)) { + this._selectorCache.set(source, new Map()) + } + const selectorMap = this._selectorCache.get(source) + + // Get or create the selector for this source + selector combination + if (selectorMap?.has(selector)) { + return ( + selectorMap.get(selector) ?? + this.createSelectorForSource(source, selector) + ) + } + + const storeSelector = this.createSelectorForSource(source, selector) + selectorMap?.set(selector, storeSelector) + + return storeSelector + } + + /** + * Create a new TanStackStoreSelector for the given source and selector without caching. + * @param source The atom or store to subscribe to + * @param selector Optional selector function to select a slice of the source state + * @returns A new TanStackStoreSelector instance that subscribes to the source and applies the selector + */ + private createSelectorForSource = ( + source: SubscribeSource, + selector?: (state: unknown) => unknown, + ): TanStackStoreSelector => { + return new TanStackStoreSelector(this.host, () => source, selector) } } diff --git a/packages/lit-table/src/reactivity.ts b/packages/lit-table/src/reactivity.ts index 01e180e246..fe4403acfc 100644 --- a/packages/lit-table/src/reactivity.ts +++ b/packages/lit-table/src/reactivity.ts @@ -1,4 +1,4 @@ -import { batch, createAtom } from '@tanstack/store' +import { batch, createAtom } from '@tanstack/lit-store' import type { TableAtomOptions, TableReactivityBindings, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ca60368c95..44cf078672 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8797,9 +8797,9 @@ importers: packages/lit-table: dependencies: - '@tanstack/store': - specifier: ^0.11.0 - version: 0.11.0 + '@tanstack/lit-store': + specifier: ^0.13.2 + version: 0.13.2(lit@3.3.3) '@tanstack/table-core': specifier: workspace:* version: link:../table-core @@ -8887,16 +8887,16 @@ importers: version: 0.5.0(@angular/core@22.0.0(@angular/compiler@22.0.0)(rxjs@7.8.2))(@types/react@19.2.16)(preact@10.29.2)(react@19.2.7)(solid-js@1.9.13)(vue@3.5.35(typescript@6.0.3)) '@tanstack/react-devtools': specifier: '>=0.10.0' - version: 0.10.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(solid-js@1.9.13) + version: 0.10.5(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.6(react@19.2.7))(react@19.2.7)(solid-js@1.9.13) '@tanstack/table-devtools': specifier: workspace:* version: link:../table-devtools '@types/react-dom': specifier: '>=18.0.0' - version: 19.2.3(@types/react@19.2.15) + version: 19.2.3(@types/react@19.2.16) react-dom: specifier: '>=18' - version: 19.2.6(react@19.2.6) + version: 19.2.6(react@19.2.7) devDependencies: '@eslint-react/eslint-plugin': specifier: ^5.8.10 @@ -13739,6 +13739,11 @@ packages: resolution: {integrity: sha512-Psl+oDiidLvtctswkTQ1P6sQIihwrMLcdfQVfkLpO42oKwxWEr1lodWUHiOG5jFXsGwDDvpUv/WAdlmJF+yGpw==} hasBin: true + '@tanstack/lit-store@0.13.2': + resolution: {integrity: sha512-uAa5gQbmPOESokHM5t+AhQ3Ye8S2/bN+PB6CHKjHXixNN6aMDSQEtHoguPoMb3a3q/NeJ2NdiseyoUg4vsh/ng==} + peerDependencies: + lit: ^3.0.0 + '@tanstack/lit-virtual@3.13.29': resolution: {integrity: sha512-fnFZ2cFcOEskbLIzNwPgN1sZ9R4xxG3wWsD8WnHRbKlUZUs9qs1lyYiakXaIHHpKedYNbhmYaP9iYYGoXwYSYQ==} peerDependencies: @@ -14107,9 +14112,6 @@ packages: peerDependencies: '@types/react': '*' - '@types/react@19.2.15': - resolution: {integrity: sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==} - '@types/react@19.2.16': resolution: {integrity: sha512-esJiCAnl0kfpNdE69f3So4WJUXy95dLZydX0KwK46riIHDzHM7O9Vtf9xCHW0PXIqvgqNrswl522kA/5yx+F4w==} @@ -17333,10 +17335,6 @@ packages: react: '>=16.6.0' react-dom: '>=16.6.0' - react@19.2.6: - resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} - engines: {node: '>=0.10.0'} - react@19.2.7: resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} engines: {node: '>=0.10.0'} @@ -23757,6 +23755,11 @@ snapshots: semver: 7.8.1 yaml: 2.8.3 + '@tanstack/lit-store@0.13.2(lit@3.3.3)': + dependencies: + '@tanstack/store': 0.11.0 + lit: 3.3.3 + '@tanstack/lit-virtual@3.13.29(lit@3.3.3)': dependencies: '@tanstack/virtual-core': 3.17.0 @@ -23809,13 +23812,13 @@ snapshots: '@tanstack/query-devtools@5.101.0': optional: true - '@tanstack/react-devtools@0.10.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(solid-js@1.9.13)': + '@tanstack/react-devtools@0.10.5(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.6(react@19.2.7))(react@19.2.7)(solid-js@1.9.13)': dependencies: '@tanstack/devtools': 0.12.2(csstype@3.2.3)(solid-js@1.9.13) - '@types/react': 19.2.15 - '@types/react-dom': 19.2.3(@types/react@19.2.15) - react: 19.2.6 - react-dom: 19.2.6(react@19.2.6) + '@types/react': 19.2.16 + '@types/react-dom': 19.2.3(@types/react@19.2.16) + react: 19.2.7 + react-dom: 19.2.6(react@19.2.7) transitivePeerDependencies: - bufferutil - csstype @@ -24222,10 +24225,6 @@ snapshots: '@types/range-parser@1.2.7': {} - '@types/react-dom@19.2.3(@types/react@19.2.15)': - dependencies: - '@types/react': 19.2.15 - '@types/react-dom@19.2.3(@types/react@19.2.16)': dependencies: '@types/react': 19.2.16 @@ -24234,10 +24233,6 @@ snapshots: dependencies: '@types/react': 19.2.16 - '@types/react@19.2.15': - dependencies: - csstype: 3.2.3 - '@types/react@19.2.16': dependencies: csstype: 3.2.3 @@ -28005,9 +28000,9 @@ snapshots: optionalDependencies: '@types/react': 19.2.16 - react-dom@19.2.6(react@19.2.6): + react-dom@19.2.6(react@19.2.7): dependencies: - react: 19.2.6 + react: 19.2.7 scheduler: 0.27.0 react-dom@19.2.7(react@19.2.7): @@ -28080,8 +28075,6 @@ snapshots: react: 19.2.7 react-dom: 19.2.7(react@19.2.7) - react@19.2.6: {} - react@19.2.7: {} readable-stream@2.3.8: