UNPKG

@tanstack/svelte-table

Version:

Headless UI for building powerful tables & datagrids for Svelte.

75 lines (74 loc) 2.88 kB
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; }