@tanstack/lit-table
Version:
Headless UI for building powerful tables & datagrids for Lit.
93 lines (91 loc) • 3.82 kB
JavaScript
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 };