@tanstack/svelte-table
Version:
Headless UI for building powerful tables & datagrids for Svelte.
75 lines (74 loc) • 2.88 kB
JavaScript
import { constructTable } from '@tanstack/table-core';
import { untrack } from 'svelte';
import { flatMerge, mergeObjects } from './merge-objects';
import { svelteReactivity } from './reactivity.svelte';
/**
* Creates a Svelte 5 table instance backed by rune-aware TanStack Store atoms.
*
* Read a specific state slice with `table.atoms.<slice>.get()` and read the
* complete state with `table.store.get()`. Those reads participate in Svelte
* dependency tracking when they run in a template, `$derived`, or `$effect`.
* The adapter syncs options in `$effect.pre`, so reactive option getters and
* external `$state` values are applied before DOM updates read table APIs such
* as `getRowModel()`.
*
* @example
* ```svelte
* <script lang="ts">
* const table = createTable({
* features,
* columns,
* get data() {
* return data
* },
* })
*
* const pagination = $derived(table.atoms.pagination.get())
* const stateJson = $derived(JSON.stringify(table.store.get(), null, 2))
* </script>
*
* <span>Page {pagination.pageIndex + 1}</span>
* <pre>{stateJson}</pre>
* ```
*/
export function createTable(tableOptions) {
// 1. Merge reactivity into options using mergeObjects (preserves getters)
const mergedOptions = mergeObjects(tableOptions, {
features: {
coreReactivityFeature: svelteReactivity(),
...tableOptions.features,
},
});
// 2. Set up resolved options with mergeOptions handler
const resolvedOptions = mergeObjects({
mergeOptions: (defaultOptions, newOptions) => {
return flatMerge(defaultOptions, newOptions);
},
}, mergedOptions);
// 3. Construct table
const table = constructTable(resolvedOptions);
// 4. Sync options reactively. When controlled state changes (e.g., $state
// inside createTableState), the effect re-runs and calls setOptions.
// Use $effect.pre so the table sees updated options BEFORE the DOM renders,
// ensuring getRowModel() returns current data (not stale, one-frame-behind data).
// Reactive option getters and nested state keys are read OUTSIDE untrack so
// they become dependencies. The setOptions call is INSIDE untrack so option
// writes do not subscribe this effect to table internals.
$effect.pre(() => {
// Resolve every option outside untrack so getter-backed data, columns,
// callbacks, metadata, and state become dependencies of this effect.
const nextOptions = flatMerge(mergedOptions);
const state = nextOptions.state;
if (state) {
for (const key in state) {
void state[key];
}
}
untrack(() => {
table.setOptions((prev) => {
return flatMerge(prev, nextOptions);
});
});
});
return table;
}