@tanstack/svelte-table
Version:
Headless UI for building powerful tables & datagrids for Svelte.
95 lines (94 loc) • 3.48 kB
JavaScript
import { untrack } from 'svelte';
import { batch, createAtom } from '@tanstack/svelte-store';
const optionsStoreDebugName = 'table/optionsStore';
function observerToCallback(observerOrNext) {
return typeof observerOrNext === 'function'
? observerOrNext
: (value) => observerOrNext.next?.(value);
}
function subscribeToRune(getValue, observerOrNext) {
const callback = observerToCallback(observerOrNext);
const unsubscribe = $effect.root(() => {
$effect(() => {
const value = getValue();
untrack(() => callback(value));
});
});
return { unsubscribe };
}
function createRuneWritableAtom(initialValue) {
let value = $state(initialValue);
return {
set: (updater) => {
value =
typeof updater === 'function'
? updater(value)
: updater;
},
get: () => value,
subscribe: ((observerOrNext) => {
return subscribeToRune(() => value, observerOrNext);
}),
};
}
/**
* Creates the table-core reactivity bindings used by the Svelte adapter.
*
* Table state atoms are backed by TanStack Store atoms. The options store stays
* framework-native because row-model APIs read `table.options` directly during
* render. Readonly table atoms bridge Store dependency tracking into
* `$derived.by`, so their `.get()` methods participate in Svelte dependency
* tracking when called in templates, `$derived`, or `$effect`.
*/
export function svelteReactivity() {
return {
createOptionsStore: true,
wrapExternalAtoms: false,
addSubscription: () => {
throw new Error('Feature not supported in current reactivity implementation');
},
unmount: () => {
throw new Error('Feature not supported in current reactivity implementation');
},
schedule: (fn) => queueMicrotask(() => fn()),
createReadonlyAtom: (fn, _options) => {
const storeAtom = createAtom(() => fn(), {
compare: _options?.compare,
});
let version = $state(0);
$effect(() => {
const subscription = storeAtom.subscribe(() => {
version += 1;
});
return () => subscription.unsubscribe();
});
const value = $derived.by(() => {
version;
return storeAtom.get();
});
return {
get: () => {
// Both reads are load-bearing: the Store read preserves dependency
// tracking between table atoms, while touching `value` registers the
// current Svelte reactive scope with the rune-backed bridge.
const currentValue = storeAtom.get();
value;
return currentValue;
},
subscribe: ((observerOrNext) => {
return subscribeToRune(() => value, observerOrNext);
}),
};
},
createWritableAtom: (initialValue, _options) => {
if (_options?.debugName === optionsStoreDebugName) {
return createRuneWritableAtom(initialValue);
}
return createAtom(initialValue, {
compare: _options?.compare,
});
},
untrack: untrack,
batch,
};
}