UNPKG

@tanstack/lit-table

Version:

Headless UI for building powerful tables & datagrids for Lit.

93 lines (91 loc) 3.82 kB
import { TanStackStoreSelector } from "@tanstack/lit-store"; import { AsyncDirective, directive } from "lit/async-directive.js"; import { noChange } from "lit"; //#region src/subscribe-directive.ts /** * A simple identity selector used when no specific selection is needed, * allowing the directive to subscribe to the entire state of the source. * @template T - The type of the state being passed through unchanged. */ const identitySelector = (state) => state; /** * An asynchronous Lit directive that subscribes to a `@tanstack/lit-store` * source and triggers re-renders specifically for the template portion it wraps. * * It uses a "fake" `ReactiveControllerHost` to bridge the gap between * TanStack's standard controller requirements and the `AsyncDirective` lifecycle. */ var SubscribeDirective = class extends AsyncDirective { /** The `TanStackStoreSelector` controller that manages the subscription to the store/atom */ controller; /** The latest source and selector used to determine if a new subscription is needed on updates */ latestSource; latestSelector; resolvedTemplate; initialized = false; render(_source, _selectorOrTemplate, _template) { /** The actual rendering is handled in the {@link update} method to ensure that template is only evaluated if needed */ return noChange; } update(_part, args) { const [source, selectorOrTemplate, template] = args; const isIdentitySubscription = template === void 0; const selector = isIdentitySubscription ? identitySelector : selectorOrTemplate; const actualTemplate = isIdentitySubscription ? selectorOrTemplate : template; const sourceChanged = this.latestSource !== source; const selectorChanged = this.latestSelector !== selector; const shouldReinitialize = !this.initialized || sourceChanged || selectorChanged; this.resolvedTemplate = actualTemplate; if (shouldReinitialize) { if (this.initialized) { this.controller?.hostDisconnected(); this.controller = void 0; } this.latestSource = source; this.latestSelector = selector; if (!this.controller) this.controller = new TanStackStoreSelector(this.createFakeHost(), () => this.latestSource, (state) => this.latestSelector?.(state)); this.controller.hostUpdate(); this.initialized = true; } return this.resolvedTemplate?.(this.latestSelector(this.latestSource.get())); } /** Cleans up the controller subscription when the directive is removed from the DOM. */ disconnected() { this.controller?.hostDisconnected(); } /** Restores the controller subscription when the directive is re-attached to the DOM. */ reconnected() { this.controller?.hostUpdate(); if (this.resolvedTemplate && this.controller) this.setValue(this.resolvedTemplate(this.controller.value)); } /** * Creates a mock `ReactiveControllerHost` allowing the `TanStackStoreSelector` * to plug into the `AsyncDirective`'s update cycle using `setValue()`. */ createFakeHost() { return { addController: () => {}, removeController: () => {}, requestUpdate: () => { if (this.resolvedTemplate && this.controller) this.setValue(this.resolvedTemplate(this.controller.value)); }, get updateComplete() { return Promise.resolve(true); } }; } }; /** * A Lit directive that subscribes to a source (Store or Atom) * and efficiently updates only the wrapped template * when the state or selected slice changes. * @example * ```ts * // Without a selector (subscribes to entire state) * html`<div>${subscribe(myStore, (state) => html`<span>${state.count}</span>`)}</div>` * * // With a selector (only updates when `count` changes) * html`<div>${subscribe(myStore, state => state.count, (count) => html`<span>${count}</span>`)}</div>` * ``` */ const subscribe = directive(SubscribeDirective); //#endregion export { SubscribeDirective, subscribe };