@tanstack/lit-table
Version:
Headless UI for building powerful tables & datagrids for Lit.
130 lines (128 loc) • 3.75 kB
JavaScript
import { FlexRender } from "./flexRender.js";
import { litReactivity } from "./reactivity.js";
import { subscribe } from "./subscribe-directive.js";
import { constructTable } from "@tanstack/table-core";
import { createRenderPhaseSource } from "@tanstack/table-core/reactivity";
import { table_publishExternalState, table_setOptions } from "@tanstack/table-core/static-functions";
import { shallow } from "@tanstack/lit-store";
//#region src/TableController.ts
/**
* A Lit ReactiveController for TanStack Table integration.
*
* Uses `constructReactivityFeature` from table-core to properly integrate
* with the TanStack Store reactivity system, matching the pattern used by
* all other framework adapters (React, Vue, Solid, Svelte, Angular).
*
* @example
* ```ts
* @customElement('my-table')
* class MyTable extends LitElement {
* private tableController = new TableController<typeof features, Person>(this)
*
* protected render() {
* const table = this.tableController.table(
* {
* features,
* columns,
* data,
* },
* (state) => ({ sorting: state.sorting }),
* )
* // use table in your template...
* }
* }
* ```
*/
var TableController = class {
host;
_table = null;
_rootSource;
_storeSubscription;
_capturedState;
_capturedSnapshot;
_hasSelector = false;
_latestSelector;
_lastSelected;
constructor(host) {
(this.host = host).addController(this);
}
/**
* Returns the Lit-backed table instance for the current render pass.
*
* The first call constructs the table with Lit reactivity bindings and
* subscribes the host to table state/options changes. Later calls merge new
* options into the same table instance and expose selected state through
* `table.state`.
*
* @example
* ```ts
* const table = this.tableController.table(
* { features, columns, data },
* (state) => ({ sorting: state.sorting }),
* )
* ```
*/
table(tableOptions, selector) {
if (!this._table) {
const mergedOptions = {
...tableOptions,
features: {
coreReactivityFeature: litReactivity(),
...tableOptions.features
},
mergeOptions: (defaultOptions, newOptions) => {
return {
...defaultOptions,
...newOptions
};
}
};
this._table = constructTable(mergedOptions);
this._rootSource = createRenderPhaseSource(this._table.store, shallow);
this._setupSubscriptions();
}
table_setOptions(this._table, (prev) => ({
...prev,
...tableOptions
}), { syncExternalState: false });
this._capturedState = this._table.options.state;
const renderSnapshot = this._rootSource.get();
this._capturedSnapshot = renderSnapshot;
this._hasSelector = selector !== void 0;
this._latestSelector = selector;
this._lastSelected = selector ? selector(renderSnapshot) : renderSnapshot;
return {
...this._table,
subscribe,
FlexRender,
get state() {
return selector?.(renderSnapshot) ?? renderSnapshot;
}
};
}
_setupSubscriptions() {
if (this._table && !this._storeSubscription) this._storeSubscription = this._rootSource.subscribe((state) => {
if (this._hasSelector) {
const nextSelected = this._latestSelector(state);
if (shallow(this._lastSelected, nextSelected)) return;
this._lastSelected = nextSelected;
}
this.host.requestUpdate();
});
}
hostConnected() {
this._setupSubscriptions();
if (this._table) this.host.requestUpdate();
}
hostUpdated() {
if (!this._table) return;
this._rootSource.markCommitted(this._capturedSnapshot);
table_publishExternalState(this._table, this._capturedState ?? null, shallow);
}
hostDisconnected() {
this._storeSubscription?.unsubscribe();
this._storeSubscription = void 0;
}
};
//#endregion
export { TableController };