|
1 | 1 | import { constructTable } from '@tanstack/table-core' |
| 2 | +import { TanStackStoreSelector } from '@tanstack/lit-store' |
2 | 3 | import { litReactivity } from './reactivity' |
3 | 4 | import { FlexRender } from './flexRender' |
4 | | -import type { Atom, ReadonlyAtom, ReadonlyStore, Store } from '@tanstack/store' |
| 5 | +import type { |
| 6 | + Atom, |
| 7 | + ReadonlyAtom, |
| 8 | + ReadonlyStore, |
| 9 | + Store, |
| 10 | +} from '@tanstack/lit-store' |
5 | 11 | import type { |
6 | 12 | NoInfer, |
7 | 13 | RowData, |
@@ -146,6 +152,13 @@ export class TableController< |
146 | 152 | private _storeSubscription?: { unsubscribe: () => void } |
147 | 153 | private _optionsSubscription?: { unsubscribe: () => void } |
148 | 154 | private _notifier = 0 |
| 155 | + private _selectorCache = new WeakMap< |
| 156 | + SubscribeSource<unknown>, |
| 157 | + Map< |
| 158 | + ((state: unknown) => unknown) | undefined, |
| 159 | + TanStackStoreSelector<unknown> |
| 160 | + > |
| 161 | + >() |
149 | 162 |
|
150 | 163 | constructor(host: ReactiveControllerHost) { |
151 | 164 | ;(this.host = host).addController(this) |
@@ -205,23 +218,28 @@ export class TableController< |
205 | 218 | const tableInstance = this._table |
206 | 219 |
|
207 | 220 | // Attach Subscribe function |
208 | | - const Subscribe = function Subscribe(props: { |
| 221 | + const Subscribe = ((props: { |
209 | 222 | source?: SubscribeSource<unknown> |
210 | 223 | selector?: (state: unknown) => unknown |
211 | 224 | children: |
212 | 225 | | ((state: Readonly<unknown>) => TemplateResult | string) |
213 | 226 | | TemplateResult |
214 | 227 | | string |
215 | | - }): TemplateResult | string { |
| 228 | + }): TemplateResult | string => { |
216 | 229 | const source = props.source ?? tableInstance.store |
217 | | - const value = source.get() |
218 | | - const selectedState = |
219 | | - props.selector !== undefined ? props.selector(value) : value |
| 230 | + |
| 231 | + const storeSelector: TanStackStoreSelector<unknown> = |
| 232 | + this._getOrCreateSelector(source, props.selector) |
| 233 | + |
| 234 | + // TODO: update to newest version of Tanstack Store: https://github.com/TanStack/store/pull/329 |
| 235 | + const selectedState = storeSelector.value |
| 236 | + |
220 | 237 | if (typeof props.children === 'function') { |
221 | 238 | return props.children(selectedState as Readonly<unknown>) |
222 | 239 | } |
| 240 | + |
223 | 241 | return props.children |
224 | | - } as LitTable<TFeatures, TData, TSelected>['Subscribe'] |
| 242 | + }) as LitTable<TFeatures, TData, TSelected>['Subscribe'] |
225 | 243 |
|
226 | 244 | return { |
227 | 245 | ...this._table, |
@@ -257,5 +275,56 @@ export class TableController< |
257 | 275 | this._storeSubscription = undefined |
258 | 276 | this._optionsSubscription?.unsubscribe() |
259 | 277 | this._optionsSubscription = undefined |
| 278 | + this._selectorCache = new WeakMap() |
| 279 | + } |
| 280 | + |
| 281 | + /** |
| 282 | + * Get or create a TanStackStoreSelector for the given source and selector. |
| 283 | + * |
| 284 | + * Caches selectors by source (WeakMap) and selector function to avoid |
| 285 | + * creating new controllers on every render cycle. |
| 286 | + * |
| 287 | + * @param source The atom or store to subscribe to |
| 288 | + * @param selector Optional selector function to select a slice of the source state |
| 289 | + * @returns A cached TanStackStoreSelector instance that subscribes to the source and applies the selector |
| 290 | + */ |
| 291 | + private _getOrCreateSelector = ( |
| 292 | + source?: SubscribeSource<unknown>, |
| 293 | + selector?: (state: unknown) => unknown, |
| 294 | + ): TanStackStoreSelector<unknown> => { |
| 295 | + if (!source) { |
| 296 | + return new TanStackStoreSelector(this.host, () => source, selector) |
| 297 | + } |
| 298 | + |
| 299 | + if (!this._selectorCache.has(source)) { |
| 300 | + this._selectorCache.set(source, new Map()) |
| 301 | + } |
| 302 | + const selectorMap = this._selectorCache.get(source) |
| 303 | + |
| 304 | + // Get or create the selector for this source + selector combination |
| 305 | + if (selectorMap?.has(selector)) { |
| 306 | + return ( |
| 307 | + selectorMap.get(selector) ?? |
| 308 | + this.createSelectorForSource(source, selector) |
| 309 | + ) |
| 310 | + } |
| 311 | + |
| 312 | + const storeSelector = this.createSelectorForSource(source, selector) |
| 313 | + selectorMap?.set(selector, storeSelector) |
| 314 | + |
| 315 | + return storeSelector |
| 316 | + } |
| 317 | + |
| 318 | + /** |
| 319 | + * Create a new TanStackStoreSelector for the given source and selector without caching. |
| 320 | + * @param source The atom or store to subscribe to |
| 321 | + * @param selector Optional selector function to select a slice of the source state |
| 322 | + * @returns A new TanStackStoreSelector instance that subscribes to the source and applies the selector |
| 323 | + */ |
| 324 | + private createSelectorForSource = ( |
| 325 | + source: SubscribeSource<unknown>, |
| 326 | + selector?: (state: unknown) => unknown, |
| 327 | + ): TanStackStoreSelector<unknown> => { |
| 328 | + return new TanStackStoreSelector(this.host, () => source, selector) |
260 | 329 | } |
261 | 330 | } |
0 commit comments